Expose a port over HTTP#
Give a port inside your instance an HTTPS address. Pick one section:
Tailscale: a private address that only your own devices can reach.
Cloudflare: an address you can send to other people.
Each step ends with a Check. Don’t move on until it passes.
The commands use port 8080; replace it with your port everywhere. Before you start, confirm the service answers inside the instance:
curl -sI http://localhost:8080
Check: the first line is a status such as HTTP/1.1 200 OK. Note the code (200 here): every later check expects the same code through the tunnel. If you see Connection refused, the service isn’t running or is on another port, and no tunnel will fix that.
Tailscale#
Tailscale joins the instance to your tailnet, the private network of your own devices. Serve gives the port an HTTPS address that only devices on your tailnet can reach. Funnel makes that same address public. You need a free Tailscale account, plus Tailscale installed and signed in on the laptop or phone you’ll test from.
Connect the instance#
Install Tailscale in the instance:
curl -fsSL https://tailscale.com/install.sh | sh
Check:
tailscale versionprints a version number.Sign the instance in to your tailnet:
sudo tailscale up
Open the URL it prints, sign in with the same account you use on your laptop, and approve the device.
Check:
tailscale statuslists this instance with a100.x.y.zaddress, and so does the Tailscale admin console’s Machines page.If
tailscale upfails with an error about/dev/net/tunor a TUN device, the container doesn’t provide one. Switch Tailscale to userspace networking and run step 2 again:sudo sed -i 's/^FLAGS=.*/FLAGS="--tun=userspace-networking"/' /etc/default/tailscaled sudo systemctl restart tailscaled sudo tailscale up
Serve and Funnel work the same in this mode.
Private address: Serve#
Give the port an address on your tailnet:
sudo tailscale serve --bg 8080
The first time, it prints a link to enable HTTPS certificates for your tailnet. Open the link, enable HTTPS, and run the command again.
--bgkeeps Serve running after you log out and across restarts.Check:
tailscale serve statusshows your address and where it points:https://<machine>.<tailnet>.ts.net (tailnet only) |-- / proxy http://127.0.0.1:8080
Test it from your laptop, which must be on the tailnet:
curl -sI https://<machine>.<tailnet>.ts.net
Check: you get the same status code as the
localhostcheck (the protocol shows asHTTP/2). The very first request can take up to a minute while the certificate is issued. Then open the address in a browser.
If only your own devices need to reach the port, you’re done.
Public address: Funnel#
Warning
Funnel has no sign-in
Anyone who has the address reaches your service. Before you continue, turn on the service’s own login: a JupyterLab token, a Gradio auth=, or a vLLM --api-key. If the people who need access have a Google, GitHub or email address you can list, use Cloudflare Tunnel with Access instead.
Make the address public:
sudo tailscale funnel --bg 8080
The first time, it prints a link to allow Funnel for this device. Open the link, approve, and run the command again.
Check:
tailscale funnel statusshows the same address marked(Funnel on).Test from a device that isn’t on your tailnet, such as your phone on mobile data with Wi-Fi and Tailscale off.
Check: the page loads at
https://<machine>.<tailnet>.ts.net. A brand-new name can take a few minutes to start resolving publicly, so wait and retry before you assume it failed.
Turn it off#
Remove the address:
sudo tailscale funnel reset # if you used Funnel sudo tailscale serve reset
Check:
tailscale serve statusreports no config, and reloading the address on your laptop fails.
To remove the instance from your tailnet completely, run sudo tailscale logout. Deleting an instance doesn’t remove its entry from the admin console’s Machines page; delete that entry there.
Cloudflare#
cloudflared connects out from the instance to Cloudflare, and Cloudflare serves your port at an HTTPS address. A Quick Tunnel needs no account and gives you a temporary trycloudflare.com address. A named Tunnel with Access gives you a permanent address on your own domain and only lets in the people you list.
Install cloudflared#
Add Cloudflare’s package repository and install it in the instance:
sudo mkdir -p --mode=0755 /usr/share/keyrings curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \ | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' \ | sudo tee /etc/apt/sources.list.d/cloudflared.list sudo apt-get update && sudo apt-get install -y cloudflared
Check:
cloudflared --versionprints a version number.
Temporary address: Quick Tunnel#
Start the tunnel. It runs in the foreground, so start it inside
tmux(or keep this SSH window open) for as long as you need the address:cloudflared tunnel --url http://localhost:8080
Check: within a few seconds the output prints a box containing your address:
Your quick Tunnel has been created! Visit it at (it may take some time to be reachable): https://<random-words>.trycloudflare.com
Test it from your laptop:
curl -sI https://<random-words>.trycloudflare.com
Check: you get the same status code as the
localhostcheck (the protocol shows asHTTP/2). If the service streams its replies, as most chat interfaces do, also send one real request in the browser and watch the whole reply arrive. Quick Tunnels don’t carry Server-Sent Events, so a page can load and then never finish a reply. If that happens, use the named tunnel below.Before you send the address to anyone, know that the link itself is the only key: anyone who has it gets in. You get a new address every time you run the command, and there is no uptime guarantee.
Turn it off: press
Ctrl+Cin the tunnel’s window.Check: reloading the address on your laptop fails.
Permanent address: Tunnel with Access#
You need a free Cloudflare account and a domain whose DNS Cloudflare manages. The steps use app.example.com; replace it with your hostname. Do steps 3 and 4 in this order, so your address never works without a sign-in.
Create the tunnel. In the Cloudflare dashboard, go to Networking → Tunnels → Create a tunnel, choose Cloudflared, and name it after the service. On the install screen, choose Debian and copy the command that installs
cloudflaredas a service. It looks like this:sudo cloudflared service install <TOKEN>
Run it in the instance. You already installed
cloudflaredin step 1, so skip the dashboard’s install lines and run only this one.Check:
systemctl is-active cloudflaredprintsactive, and the tunnel shows Healthy in the dashboard.Choose who can sign in. Go to Zero Trust → Access controls → Applications → Add an application → Self-hosted. Set the public hostname to
app.example.com, and add an Allow policy that lists your visitors’ email addresses or email domain.Check: the application appears in the list with your hostname and policy.
Publish the port. Back in the tunnel, choose Add route → Published application: subdomain
app, your domain, and service URLhttp://localhost:8080.Check: from your laptop, the address redirects to Cloudflare’s sign-in page instead of your service:
curl -sI https://app.example.com
The status is
302and thelocationheader points to<your-team>.cloudflareaccess.com. If you get your service’s own status code instead, the Access application from step 3 doesn’t cover this hostname. Delete the route, fix the hostname in step 3, and publish the route again.Sign in as a real visitor. Open
https://app.example.comin a private browser window and sign in with an address on your Allow list.Check: your service loads after sign-in. Repeat with an address that isn’t on the list, and confirm it is refused.
The address stays the same and
cloudflaredrestarts with the instance. To add or remove visitors, edit the Access policy; you don’t need to touch the instance.Turn it off: in the tunnel, delete the published route. To remove the connector from the instance too, run
sudo cloudflared service uninstall.Check: reloading
https://app.example.comfails. If you left the route in place, the address is still live.
The tunnel, route and Access policy live in your Cloudflare account, but cloudflared lives in the instance. On a new instance, repeat step 1 and step 2’s install command with the same token, and the address works again.
For more on when to choose each method, and on SSH port forwarding for access that’s only for you, see Expose a service.