Skip to main content

Module 07 — Tailscale Funnel

Objective

Expose the Customer Information App to the internet using Tailscale Funnel. By the end of this module, anyone with the public URL can access the application — no inbound port forwarding, DNS configuration, or domain required.

Prerequisites

  • Module 06 complete — Nginx reverse proxy working on web-server
  • App accessible at http://192.168.56.13 through Nginx
  • A free Tailscale account (sign up at https://login.tailscale.com/start)

1. What is Tailscale?

Tailscale is a mesh VPN built on WireGuard. It creates a private network (called a "tailnet") across your devices — each device gets a stable IP address (like 100.x.y.z) and can reach every other device on the tailnet, regardless of firewalls or NAT.

Tailscale Funnel extends this by exposing a service on your tailnet to the public internet. Tailscale gives you a free HTTPS URL (https://<hostname>.<tailnet>.ts.net) that anyone can access — even without Tailscale installed.

How the traffic flows:

Internet (browser)
|
| HTTPS request
v
Tailscale Funnel (public endpoint)
|
| Forwarded to your node
v
web-server (192.168.56.13:80)
|
| Nginx proxies to app-server
v
Go app (app-server 192.168.56.12:8080)
|
| Query
v
PostgreSQL (db-server 192.168.56.11:5432)

Key benefits:

  • No inbound ports — your router and firewall stay closed. Tailscale handles everything.
  • No domain required — you get a free *.ts.net HTTPS URL automatically.
  • Built-in HTTPS — Tailscale provisions TLS certificates. Visitors connect over HTTPS.
  • Zero configuration — no config files, no credentials files, no DNS records to manage.

2. Install Tailscale on web-server

SSH into web-server:

ssh web-server

Install Tailscale using the official install script:

curl -fsSL https://tailscale.com/install.sh | sh

Connect to your tailnet:

sudo tailscale up

This will print a URL. Copy the URL and open it in a browser on your Mac (web-server has no GUI). Log in to your Tailscale account to authorize the device.

Verify the connection:

tailscale status

You should see your web-server listed with a 100.x.y.z IP address.

Checkpoint: web-server is connected to your tailnet and shows a Tailscale IP.


3. Enable Tailscale Funnel

Tailscale Funnel must be enabled in your Tailscale admin console before it can be used.

Enable Funnel in the admin console

  1. Open https://login.tailscale.com/admin/settings/features in your browser
  2. Find Funnel and enable it
  3. Under Funnel policy, make sure your web-server node is allowed

Start Funnel

On web-server, run:

sudo tailscale funnel 80

Tailscale will output your public URL, something like:

https://web-server.<your-tailnet>.ts.net

This URL is now publicly accessible from anywhere on the internet.

Verify:

  1. Open the URL in a browser on your Mac (or any device, including your phone)
  2. You should see the login page
  3. Log in with admin / admin123
  4. Create a customer, edit it, delete it — full CRUD test
  5. Try opening the URL on a different device or share it with someone

Checkpoint: The application is accessible from the internet via your *.ts.net URL over HTTPS.


4. Make Funnel Persistent

The tailscale funnel command runs in the foreground. To make it persist across reboots and SSH disconnections, use the --bg flag:

sudo tailscale funnel --bg 80

This runs Funnel in the background as part of the Tailscale daemon, which is already a systemd service.

Verify the Tailscale service is enabled on boot:

sudo systemctl is-enabled tailscaled

You should see enabled.

Verify persistence:

  1. Open your *.ts.net URL in a browser — it should work
  2. Close the SSH session to web-server
  3. Refresh the browser — the app should still be accessible
  4. Reboot web-server: sudo reboot
  5. Wait 30-60 seconds, then refresh — the app should come back online

Checkpoint: Tailscale Funnel survives reboots and SSH disconnections.


5. Celebrate — Your App is Live

Your Customer Information App is now publicly accessible on the internet. Take a moment to appreciate what you have built across these seven modules:

Internet
|
v
Tailscale Funnel (HTTPS, public endpoint)
|
v
web-server: Nginx reverse proxy (192.168.56.13:80)
|
v
app-server: Go backend + frontend (192.168.56.12:8080)
|
v
db-server: PostgreSQL (192.168.56.11:5432)

Final end-to-end test:

  1. Open the public URL on your phone or share it with a friend
  2. Log in with admin / admin123
  3. Create a customer with all fields filled in
  4. Edit the customer
  5. Delete the customer
  6. Log out

What you accomplished in Phase 1:

ModuleWhat you built
01Three Ubuntu VMs with networking
02PostgreSQL database on db-server
03Go REST API on app-server
04HTML/JS frontend served by Go
05Session-based login system
06Nginx reverse proxy on web-server
07Tailscale Funnel to the internet

You deployed a 3-tier web application across 3 servers and made it publicly accessible — all from scratch.

What is next: Phase 2 will cover hardening this setup — SQL injection, credential security, brute force protection, firewall rules, CORS, and a final security audit.


Troubleshooting

"tailscale up" hangs or fails

Check that web-server has internet access:

ping -c 4 8.8.8.8

If ping fails, the VM has no internet. Check your VirtualBox network adapter settings — the VM needs a NAT or bridged adapter in addition to the host-only adapter.

Funnel URL returns "502" or "Bad Gateway"

Tailscale reached your server, but Nginx is not responding. Check Nginx:

sudo systemctl status nginx

If Nginx is down, start it:

sudo systemctl start nginx

Also verify Nginx is listening on port 80:

curl http://localhost:80

"Funnel is not enabled" error

You need to enable Funnel in the Tailscale admin console:

  1. Go to https://login.tailscale.com/admin/settings/features
  2. Enable Funnel
  3. Make sure your node's ACL policy allows Funnel

Login page loads but API calls fail

The tunnel is working (you can see the page) but something is wrong with the proxy chain. Verify Nginx is correctly proxying to the Go app:

# From web-server
curl http://localhost:80/health

If this returns an error, check the Nginx configuration and make sure the Go app is running on app-server:

ssh app-server "sudo systemctl status customerapp"

Tailscale shows "offline" after reboot

Verify the tailscaled service is running:

sudo systemctl status tailscaled

If it is not running:

sudo systemctl enable tailscaled
sudo systemctl start tailscaled

Then re-enable Funnel:

sudo tailscale funnel --bg 80