exposehub

How to use exposehub

From the first command to a domain of your own. Everything here is written for this installation: agents connect to connect.exposehub.eu and tunnels get addresses under exposehub.eu.

Get started

Three commands, once per machine. After that, the last one is all you need.

  1. Install the agent

    On macOS and Linux, without administrator rights. It goes to ~/.local/bin and checks the download's checksum.

    curl -fsSL https://connect.exposehub.eu/install.sh | sh

    On Windows, download exposehub-windows-amd64.exe or, on ARM, exposehub-windows-arm64.exe, and use it from a terminal.

  2. Log in

    Create a token on the Agent tokens page of the dashboard, one per machine, then paste it when the agent asks. It is saved for your user only and stays out of your shell history.

    exposehub login
  3. Open a tunnel

    Give it the port your service listens on. The agent prints the public address and keeps it for as long as it runs.

    exposehub http 8000
    Forwarding  https://k3v9q2mw7x.exposehub.eu  ->  http://localhost:8000
    Inspector   http://127.0.0.1:4040

Stop it with Ctrl+C. If the connection drops, the agent reconnects by itself and keeps the address. exposehub update installs the newest agent the server offers.

Share with colleagues

Send the address to anyone. They see what runs on your machine right now: your branch, your data, your latest change, without a deploy. Reload, and they see the next change.

A random address changes every time you start the agent. Pick a name to keep the same link:

exposehub http 8000 --subdomain shop

That opens https://shop.exposehub.eu. The name belongs to your team while your agent is connected and for a short time after it disconnects (a minute, unless the server is set up otherwise), so a reconnect gets it back. A teammate cannot take it while you use it; once it is free, anyone can.

Testing on your phone works the same way: open the address on the phone, on any network.

Ports and local sites

The target can be a port, a host and port, or a full URL:

exposehub http 8000
exposehub http shop.test
exposehub http https://localhost:8443

A local site with a name of its own, such as shop.test, keeps seeing that name in the Host header, so virtual hosts in your local web server keep working. The public address arrives in X-Forwarded-Host. A plain port sees the public address as Host instead. Use --host-header preserve|rewrite to change either.

Your service also gets the visitor's address in X-Forwarded-For and the scheme in X-Forwarded-Proto, as behind any proxy.

Dev servers

Vite and webpack dev servers turn away requests that name a host they do not know. The agent notices that answer, sends the dev server its own name from then on and says so in the terminal; pages and hot reload then work without any setting. With --host-header preserve the agent leaves the public name alone, and the dev server needs it in its own list of allowed hosts.

Next.js serves its pages under any name, but lets hot reload through only from addresses it allows. The agent passes the hot-reload connection of a page opened through the tunnel on as coming from the dev server's own address, and says so in the terminal; pages of other sites stay shut out. With --host-header preserve it leaves that alone and prints the allowedDevOrigins line to add to next.config.js instead.

Several tunnels at once

One agent can open up to ten tunnels, for example a shop, its API and an admin, each on its own address. Name them on the command line:

exposehub http shop=8000 api=3000 admin=8080

Each name becomes a subdomain. Flags such as --auth or --allow apply to all of them, the inspector shows them together, and every request line says which tunnel it came through.

For tunnels you start every day, put them in an exposehub.yml in your project and run exposehub start. Each tunnel can have its own settings:

tunnels:
  shop:
    target: 8000
    subdomain: shop
    auth: ["ada:${SHOP_PASSWORD}"]
    allow: ["Office VPN"]
  hooks:
    target: 8000
    subdomain: shop-hooks
    log: bodies
  docs:
    target: docs.test
    hostname: dev.example.com

exposehub start shop docs opens only those. The file is looked for in the current directory and its parents, so it works from anywhere in the project. ${NAME} reads an environment variable, so passwords stay out of the file; a variable that is not set stops the agent instead of opening a tunnel without a password.

Webhooks

Payment providers, git hosts and other services can call code running on your machine. Start a tunnel with a fixed name and enter its address in the provider's webhook settings:

exposehub http 8000 --subdomain shop-hooks
https://shop-hooks.exposehub.eu/webhooks/payment

Set a breakpoint and debug the real request. When you need it again, replay it from the inspector instead of triggering a new payment.

Inspector

While the agent runs, http://127.0.0.1:4040 shows every request through your tunnels as it happens: method, path, status and timing, and for each one the full request and answer with headers and bodies.

  • Replay sends a request to your service again, as often as you like.
  • Copy as curl gives you the request as a command to tweak and run.
  • Filter by path, method or status, and see the error share, the median and the 95th-percentile time at a glance.

Nothing of it leaves your machine. The agent keeps the last 1000 requests, up to 512 KiB of each body. If port 4040 is taken, it uses the next free port up to 4049 and prints which. --inspect off turns it off.

Request logs

The Request logs page in the dashboard keeps a summary of every request to your tunnels for 7 days: time, address, method, path, status, duration, size and the visitor's network. It works after the agent has stopped, which the inspector does not.

  • Only you see your logs. Share them with a colleague in your team on that page, and stop sharing whenever you like.
  • Values in the address that look like secrets (token, key, signature, password and the like) are stored as [redacted]. The visitor's IP address is shortened to its network.
  • Headers and bodies are kept only when you start the tunnel with --log-bodies, up to 64 KiB each, with authorization headers and cookies blanked out.
  • --no-log keeps nothing of a tunnel's requests.

Password protection

Let visitors in only with a name and password:

exposehub http 8000 --auth ada:s3cret --auth ben:0th3r

The tunnel server checks it, so a request without the right password never reaches your machine. The browser asks for it once. Your service does not see the Authorization header the password came in, so its own login keeps working.

Use it whenever a tunnel is open for longer, and always on a domain of your own: such a domain is listed publicly as soon as its certificate is issued, and scanners ask it for files like .env within seconds.

Limit to networks

A tunnel can answer only requests from certain networks, such as your company VPN. Everyone else gets a page saying the tunnel is limited to certain networks, with their own address, and the request never reaches your machine. Set it up on the Access page of your team:

  • Networks are named lists of addresses and ranges, IPv4 or IPv6, for example the exit addresses of the VPN. On the VPN, "Add my address" fills in the address you connect from.
  • The team default applies to every address without a rule of its own, random ones included: anyone, or only listed networks.
  • Saved addresses are names your team keeps: nobody else can open a tunnel on them, and each has a rule. Keep the team on the VPN and open just the address you give a payment provider for webhooks.
  • Your own domains get a rule there too.

Team owners and admins manage networks and the team default. Members save addresses and limit them further, but cannot open what the team keeps closed. Changes reach running tunnels within seconds.

On the command line, --allow limits a single run further. It never opens what the dashboard keeps closed: a visitor has to pass both.

exposehub http 8000 --allow 'Office VPN' --allow 2001:db8:42::/48

Your own domain

A team can open tunnels on a domain it controls, such as dev.example.com, with a certificate issued for it.

  1. Add it

    On the Domains page of your team. Team owners and admins can add domains.

  2. Create two DNS records

    The page shows them with your values: a TXT record that proves the domain is yours, and a record that points it at the tunnel server. Then press Check DNS.

    TXT   _exposehub.dev.example.com  exposehub-verify=…
    CNAME dev.example.com             connect.exposehub.eu

    For the top of a domain (example.com itself), DNS does not allow a CNAME; the page then asks for the address instead.

  3. Open a tunnel on it

    exposehub http 8000 --hostname dev.example.com --auth ada:s3cret

    The certificate is issued within about a minute of connecting the domain. Until then, browsers show a certificate warning.

Teams and tokens

Everyone has a personal team. Create more teams in the dashboard and invite colleagues: a team shares its addresses and domains, and each person has tokens of their own.

  • Create one token per machine and name it after it. When a laptop is lost, revoke that one token; agents using it are disconnected within seconds.
  • Members see and revoke their own tokens. Team owners and admins see and revoke everyone's, and manage the team's domains.
  • When someone leaves a team, their tokens for it stop working and their request logs in it are deleted.
  • The Tunnels page lists your agents that are connected right now. Disconnect one you left running on another machine: that agent stops, and its token keeps working. Owners and admins see and disconnect the whole team's.

Commands and flags

exposehub login
Save your agent token. It asks for it, so it never lands in your shell history.
exposehub http TARGET...
Open a tunnel to each target: a port, host[:port] or URL. NAME=TARGET asks for that subdomain.
exposehub start [NAME...]
Open the tunnels of exposehub.yml, all of them or the ones named.
exposehub update
Install the agent version the server offers.
exposehub logout
Forget the saved token. The token stays valid until you revoke it in the dashboard.
exposehub server [URL]
Show which tunnel server is used and why, or switch to another.
exposehub version
Print the agent's version.

Flags for exposehub http

--subdomain NAME
Ask for a name under exposehub.eu, for example shop. Without it the server picks a random one.
--hostname HOST
Use a domain your team connected in the dashboard instead of a subdomain.
--auth NAME:PASSWORD
Let visitors in only with this name and password. Repeat it for several people.
--allow VALUE
Let only visitors from this address, range (203.0.113.0/24) or named network of your team in. Repeat it for more. Adds to the rules in the dashboard.
--log-bodies
Keep headers and bodies in the request log too, not only the summary.
--no-log
Keep nothing of this tunnel's requests in the dashboard.
--inspect ADDR
Where the inspector listens, 127.0.0.1:4040 by default, or off.
--host-header MODE
preserve or rewrite the Host header your service sees. The default depends on the target.
--domain DOMAIN
Which tunnel domain to use, when the server offers more than one.
--server URL
Another tunnel server than the saved one. EXPOSEHUB_SERVER works too.
--token TOKEN
Another token than the saved one. EXPOSEHUB_TOKEN works too.
--verbose
Print connection details while it runs.

When something is off

Visitors see "This tunnel is reconnecting"
Your agent lost its connection and is coming back; the address is kept for it. The page retries every 5 seconds by itself.
Visitors see "Nothing is connected here"
No agent is serving that address. Start it again; the page shows the command with the right name.
Visitors see "The site behind this tunnel is not answering"
The tunnel works, but your service does not: it is stopped, still starting, or listens on another port. The page names the address the agent tried and retries every 5 seconds.
Visitors see "This tunnel is limited to certain networks"
They are not on a network the tunnel allows. The page shows their address: connect the VPN, or add the address to a network on the Access page.
The agent prints "Waiting" and that nothing answers at your local address
Nothing listens on the local port yet. The tunnel is open all the same and starts serving as soon as your service does.
"the server closed the tunnel: it was disconnected in the dashboard"
You or a team owner closed it on the Tunnels page. The token is still valid: start the agent again.
"is saved by another team"
Another team keeps that name for itself. Pick another one.
"your team has no network called …"
The name after --allow does not match a network on the Access page. Names are not case sensitive, but spelling counts; put names with spaces in quotes.
"the server rejected the token"
The token was revoked, the team was deleted or you left it. Create a new token and run exposehub login again.
"is in use by another agent of your team"
A colleague is serving that name right now. Pick another one, or ask them to stop.
"is in use by another account"
Another team uses that name at the moment. Pick another one.
The browser warns about the certificate on my own domain
The certificate is issued within about a minute of connecting the domain. If you clicked through the warning before that, close the tab or restart the browser: some browsers keep showing the site as not secure for the rest of the session.

What is stored

Requests to your tunnels go to your machine and back; their content is not stored unless you start a tunnel with --log-bodies. The request log keeps summaries for 7 days, visible only to you and those you share it with. The inspector keeps everything on your machine.

The privacy notice explains in full what is processed and why.