localhostly Português

localhostly is a Pinggy alternative with a URL that stays

If you are leaving Pinggy, here is what you get instead: one public HTTPS URL that keeps its name for the whole session and comes back after a short drop. Run npx localhostly 3000 and port 3000 answers at https://brave-amber-fox.localhostly.iblabs.io, behind NAT. The account asks for an e-mail: 2 tunnels, 1000 requests per minute and 25 GiB a month.

Get your token Install localhostly

$ npx localhostly 3000

https://brave-amber-fox.localhostly.iblabs.io → http://127.0.0.1:3000
Ctrl+C to stop. Requests:

21:14:03  GET     /                                        200 12ms
21:14:05  POST    /api/webhook                             201 31ms

The account in numbers

The five numbers below are the account's limits; traffic is counted in both directions, and the month starts again on the 1st, in UTC. At a limit, the visitor gets a 429 page that names it and says when it lifts.

One command, one public URL

Nothing is downloaded by hand: npx localhostly 3000 fetches the agent from npm and runs it. The agent opens one outgoing WebSocket connection to the localhostly server and keeps it, so no port is opened on your router or firewall. When a visitor loads your URL, the server finds the name in the address, hands the request to that connection, and the agent plays it against your local port and streams the reply back. HTTP and WebSocket both go through.

  1. Get a token. Type your e-mail in the sign-up form; the token is shown on the page, and no confirmation e-mail is sent.
  2. Save it. localhostly login <token> stores it in ~/.localhostly/config.json, readable by your user only.
  3. Open the tunnel. npx localhostly 3000, or localhostly 3000 once installed with npm i -g localhostly. The first line is the public URL; below it, one line per request with time, method, path, status and milliseconds.

Your app gets the visitor's method, path, headers and body, plus X-Forwarded-For, X-Forwarded-Proto: https and X-Forwarded-Host; --host myapp.test:3000 points the tunnel at another address and sets that Host. The name is 3 English words drawn at random, adjective, color and animal, and nobody picks it.

The URL you get at start is the URL until you press Ctrl+C

The name is drawn once, when the agent connects, and the URL printed on the first line is where every request after it arrives, for as long as the connection holds. The server pings the agent every 30 seconds and closes the connection after 60 seconds without an answer; in a measured run, a tunnel idle for 620 seconds stayed up. Ctrl+C is what closes it.

When the connection drops on the way, the agent reconnects by itself, waiting 1 second and doubling up to 60, and asks for its name back; the server keeps that name for it for 10 minutes, so the URL answers again with nothing changing on the visitor's side. After a longer drop the agent comes back under a new name and prints name changed.

Webhooks and API clients get no warning page

The first visit from a browser shows a warning page that says the site is served from someone's own computer; its button sets a cookie that keeps the page away for 24 hours. A webhook delivery, a curl call or any other API client is not a browser, so it never sees that page: the request goes straight to your app, and a client that cannot click sends the X-Localhostly-Skip-Warning header instead.

Register https://<name>.localhostly.iblabs.io/<your path> with the webhook provider; each delivery prints a line in your terminal with method, path, status and time, and a delivery that arrives while your app is down gets a 502 page saying so.

Streaming responses and big uploads pass through

Bodies move in pieces of up to 64 KiB, in both directions, so no request is ever held whole in memory; in the measured run a 20 MiB upload crossed the tunnel with peak memory under 100 MiB, and a 20 MiB download reached a slow visitor piece by piece.

A response that your app writes over time reaches the visitor as it is written: Server-Sent Events sent at 0, 3 and 6 seconds arrived at 0.6, 3.6 and 6.6 seconds in a measured stream. The one clock is on the first byte, 60 seconds; after it, a download takes as long as it takes. A visitor's WebSocket is handed to your app, and the upgrade completes once your app accepts it with the subprotocol it picked.

Lock the tunnel with a password or an IP list

--password puts a login box in front of every visitor; leave the value empty and localhostly makes a 12-character password and prints it in your terminal only. The Authorization header the visitor types never reaches your app.

--allow 203.0.113.7,10.0.0.0/8 admits those IP addresses or ranges and nobody else, IPv4 or IPv6, up to 64 entries: any other address gets a 403 page showing the address the server saw, and its WebSocket upgrade gets 403 as well. A tunnel with --allow skips the warning page, and with --password on top a visitor needs the address and the password.

Keep it running as a system service

localhostly service install 3000 makes the tunnel a service of the system: systemd on Linux, launchd on macOS, a Windows service. The service reconnects on its own, keeps the name it was given, asks for it again after a reboot and keeps trying if the server turns it down; localhostly service status says whether it is running and localhostly service uninstall removes it.

Install

localhostly runs on Node.js 24 or newer.

Both lines run the same agent from the npm registry. Save your token with localhostly login <token>; localhostly 3000 then opens the tunnel.

Get your token

Sign-up takes an e-mail address and nothing else: the token is shown on the sign-up page, no confirmation e-mail is sent, and localhostly login <token> keeps it on your computer.

Get your token