Documentation
Everything you need to share localhost with PublicLocal, from the first command to running your own server. Updated 30 October 2026 · v1.0
Install
PublicLocal is one static binary for Linux, macOS, Windows and FreeBSD (amd64 and arm64).
# Linux / macOS curl -fsSL https://publiclocal.app/install.sh | sh # Windows (PowerShell) irm https://publiclocal.app/install.ps1 | iex # Docker docker run --rm --network host ghcr.io/sevenoways/publiclocal http 3000 # Go go install github.com/sevenoways/publiclocal/cmd/publiclocal@latest
Both installers download the build for your system from GitHub Releases and verify its SHA-256 checksum before installing. Run the same command again to update.
Quick start
Start your app, then share its port:
$ publiclocal http 3000 Forwarding https://k7xq2m.publiclocal.app -> 127.0.0.1:3000 Link expires in 24 hours; run `publiclocal login` for 7-day links
Anyone with the link reaches your app over HTTPS. Press Ctrl+C to stop. If you run the command again later, you get the same link back until it expires; add --new for a different one. You can also forward to another host on your network: publiclocal http 192.168.1.20:8080.
Browsers see a short "temporary developer link" notice the first time they open a link on the free server. Webhooks, API calls and curl go straight through, and you can skip the notice in any client by sending the header PublicLocal-Skip-Warning: 1.
Watch requests, replay webhooks
While a tunnel runs, every request is printed as it is served (--quiet turns it off), and the inspector at localhost:4040 shows each one with headers and bodies. Select a request and click Replay against localhost to send it to your app again, which is how you fix a webhook handler without triggering the webhook over and over. --inspect "" turns the inspector off; --inspect 127.0.0.1:5050 moves it.
14:02:11 POST /webhooks/stripe 200 12 ms 54.187.174.169 14:02:13 GET /api/orders/42 404 3 ms 203.0.113.7 ^C Served 2 requests from 2 visitors · 4 KB · top: /webhooks/stripe (1), /api/orders/42 (1)
Passwords, notes, self-destructing links, QR codes
$ publiclocal http --password s3cret 3000 # visitors must enter the password (any user name) $ publiclocal http --max-visits 1 3000 # only the first person who opens it can use it $ publiclocal http --expires 30m 3000 # gone after half an hour $ publiclocal http --note "Staging build for Anna" 3000 $ publiclocal http --qr 3000 # QR code in the terminal, for your phone $ publiclocal http --cors 3000 # permissive CORS headers for browser apps $ publiclocal http 3000 8080 # two links in one go
The password is checked by the server before anything reaches your app, and your app never sees the password header. The note appears on the first page visitors see, together with your GitHub name if you are signed in. --max-visits counts visitor networks: the first N people who open the link keep access, everyone else gets "visitor limit reached".
Share a folder or a file
$ publiclocal share ./build # a folder, with a listing $ publiclocal share report.pdf # a single file, served at /
No web server needed: PublicLocal serves the files itself. Folders with an index.html are served as a site; others get a tidy listing. Dotfiles are hidden. All the link flags above apply, so publiclocal share --password s3cret --max-visits 1 report.pdf hands one file to one person.
Link status, badge and stats
Every link answers /.publiclocal/status with JSON (state, expires, requests, visitors, bytes) and /.publiclocal/badge.svg with a small preview: online / offline / expired badge for a README or pull request. When a link is offline or has expired, visitors see a page saying so, with when it was live and how much traffic it served.
Sign in for 7-day links
$ publiclocal login # opens GitHub sign-in; type the code shown in your terminal $ publiclocal http 3000 # links now last 7 days $ publiclocal extend k7xq2m # another 7 days, as often as you like $ publiclocal links # your links and when they expire $ publiclocal logout
Sign-in reads only your public GitHub profile and is used to tie links to you. GitHub accounts younger than 30 days cannot sign in, which keeps throwaway accounts out. To take a link back by name after a restart, use publiclocal http --link k7xq2m 3000.
Command reference
| Command | What it does |
|---|---|
publiclocal http <port> [port...] | Public HTTPS links for local HTTP servers. Flags: --password, --max-visits, --expires, --note, --cors, --qr, --inspect, --quiet, --new, --link NAME, --subdomain NAME (own server), --server HOST, --token. |
publiclocal share <path> | Serve a folder or file from this computer and share it. Same flags as http. |
publiclocal tcp <port> | Public TCP port for SSH, databases or game servers. Self-hosted servers only. --remote-port N asks for a fixed port. |
publiclocal login / logout | Sign in with GitHub for 7-day links; sign out and revoke the token. |
publiclocal links | List your links, whether they are online, and when they expire. |
publiclocal extend <link> | Push a link's expiry to 7 days from now (signed in). |
publiclocal mcp | Run the MCP server for AI assistants. Flags: --allow-ports 3000,5173, --max-tunnels. |
publiclocal server | Run your own tunnel server. See self-hosting. |
publiclocal admin | On your own server: list, kill, ban, suspend, flags, scan. |
publiclocal version | Print the version. |
Environment variables: PUBLICLOCAL_SERVER, PUBLICLOCAL_TOKEN (own server), PUBLICLOCAL_ACCOUNT (login token). Without --server the client uses the free server at publiclocal.app.
Self-hosting on your own domain
Your own server gives you addresses on your domain, names you choose, no expiry, TCP tunnels and your own limits. Any small VPS with a public IP works; bandwidth matters more than CPU.
1. DNS
Point a name and its wildcard at the server (the wildcard is what gives each tunnel its own subdomain):
tunnel.example.com. A 203.0.113.10 *.tunnel.example.com. A 203.0.113.10
2. Server
Open ports 80, 443, 7000 and the TCP range (default 10000–20000), then:
curl -fsSL https://publiclocal.app/install.sh | sh publiclocal server --domain tunnel.example.com --tokens "$(openssl rand -hex 24)" --acme-email you@example.com
Certificates are issued by Let's Encrypt per subdomain on first use. Busy servers can use one wildcard certificate instead with --cloudflare-token (a Cloudflare API token that may edit the zone's DNS). A systemd unit and Docker image are in the public-instance guide.
3. Clients
export PUBLICLOCAL_SERVER=tunnel.example.com export PUBLICLOCAL_TOKEN=the-token-from-above publiclocal http --subdomain myapp 8080 # https://myapp.tunnel.example.com publiclocal tcp 22 # tcp://tunnel.example.com:1xxxx publiclocal tcp --remote-port 15432 5432
Server flags
| Flag | Default | Meaning |
|---|---|---|
--domain | Base domain; tunnels are served at name.domain. Required. | |
--tokens | Comma-separated tokens clients must present. Empty allows anyone. | |
--control | :7000 | Port clients connect to. |
--http, --https | :80, :443 | Set --https "" to run plain HTTP behind your own proxy (clients then need --tls=false). |
--tcp-ports | 10000-20000 | Port range for TCP tunnels. |
--cloudflare-token | One wildcard certificate via Cloudflare DNS instead of a certificate per name. | |
--state-file | Keep links and accounts across restarts. | |
--public | off | Safeguards for an open instance: random names only, no TCP, warning page, limits, misuse checks. Needs --abuse-contact. |
--max-tunnel-age | never (public: 24h) | Link lifetime. |
--github-client-id, --github-client-secret | Enable publiclocal login with your own GitHub OAuth app. | |
--admin, --admin-token | 127.0.0.1:7001 | Local admin API for publiclocal admin. |
The full list is in publiclocal server -h, and the guide for running an open public instance (abuse handling, Public Suffix List, CAA records) is in the repository.
AI assistants (MCP)
PublicLocal ships an MCP server with three tools: share_local_port, list_tunnels and stop_tunnel. It runs on your machine, started by the assistant, because the tunnel has to begin where your app runs. Tunnels close when the assistant session ends.
# Claude Code claude mcp add publiclocal -- publiclocal mcp # OpenAI Codex CLI codex mcp add publiclocal -- publiclocal mcp # Claude Desktop (claude_desktop_config.json), Cursor (.cursor/mcp.json), Gemini CLI (~/.gemini/settings.json) { "mcpServers": { "publiclocal": { "command": "publiclocal", "args": ["mcp"] } } } # VS Code (.vscode/mcp.json) { "servers": { "publiclocal": { "type": "stdio", "command": "publiclocal", "args": ["mcp"] } } }
Add "--allow-ports", "3000,5173" to args so the assistant can only ever share the ports you list. ChatGPT's apps connect only to remote MCP servers and cannot start programs on your computer, so use the Codex CLI for OpenAI models. Full details: MCP guide.
Free server limits
- Random names, 6–8 characters. Links last 24 hours, or 7 days signed in (extendable).
- HTTP and HTTPS only. TCP needs your own server.
- 3 tunnels at a time and 10 active links per network (50 per account); 600 requests per minute per tunnel; 2 GB per session; 10 MB per request.
- Browsers see a notice before tunnel content. Random live links are checked for phishing. See fair use.
Troubleshooting
"local service unavailable" when opening the link: nothing is listening on the port you shared, or your app only listens on a specific interface. Check with curl localhost:3000.
Your app redirects to the wrong host: the original Host header is passed through, and X-Forwarded-Proto: https is set. Frameworks that check allowed hosts (Django ALLOWED_HOSTS, Rails config.hosts, Vite server.allowedHosts) need the tunnel host added.
Webhooks hit the warning page: they shouldn't; the notice is only for browsers. If a service sends a browser-like user agent, add the header PublicLocal-Skip-Warning: 1 to its request if you can, or self-host without --public.
"This app can't run on your PC" on Windows: you downloaded the ARM64 build. Use the installer command, which picks the right one.
Anything else: open an issue.