Private Until It Isn’t: Frictionless Exposure via Tailscale Funnels
- Andrew Loera
- Feb 17
- 9 min read
Tailscale is a service that promises private, peer-to-peer connectivity using WireGuard. Designed with simplicity in mind, this plug-and-play VPN alternative has quickly gained traction with sysadmins, businesses, and homelab enthusiasts alike. With a private, easy-to-use, and scalable virtual meshnet, many Tailscale users choose to self-host tools (e.g. Nextcloud, Grafana, Home Assistant), all with the confidence that these deployments remain private within the mesh.
But what happens when these same users, chasing convenience, allow their private services
to quietly face the open web?
This question, sparked by Tailscale’s Funnel feature, is what I explore in this post!
Funnels and the Illusion of Privacy
Tailscale makes privacy feel effortless. You install it, join a tailnet, and suddenly your devices are talking like old friends. Traffic is encrypted, connections are low-latency and peer-to-peer, and it all works without punching holes through firewalls or wrangling with NAT. No open ports. No messy configs. Just some *.ts.net address that magically works.
By default, Tailscale routes everything within a private mesh. Device hostnames (e.g. andrew-laptop.*.ts.net, nextcloud.*.ts.net) are resolved using MagicDNS. The * shown can represent multiple things, but most commonly are either:
Default Tailnet Names: tail<ID> or tailnet-<ID> where <ID> is a randomly generated hex string, or
Fun Tailnet Names: Two random strings such as cat-crocodile.
Both of these methods increase entropy by obfuscating or mnemonizing the host. Either way, these domains are not visible to the outside world. Unless you’re in the mesh, they simply don’t exist. No public DNS records, no public TLS, no internet exposure.
Introducing: Serves and Funnels
Serves and Funnels are both Tailscale features that allow users to expose HTTPS services on their devices. Devices using the Serve or Funnel features follow the same *.ts.net hostname scheme defined above, making them ideal for simple web hosting. Different from a regular machine on the mesh, Serves are accessible through Tailscale’s MagicDNS and Funnels are accessible through public DNS — accessible securely, but publicly. When enabling a Funnel, Tailscale creates a real DNS record and issues a public HTTPS certificate for your tailnet node, exposing your local service to the world. Now, you do not have to be within the mesh to access services in the mesh.
To recap, when a Funnel is enabled, the associated *.ts.net hostname gets:
A real DNS record (globally resolvable)
A public HTTPS certificate (logged in certificate transparency logs)
By toggling a single checkbox, you’ve now shifted from mesh-only privacy to public exposure. No configs. No testing. No second thoughts.
So, if these Funnels are:
Discoverable through certificate transparency logs
Resolvable via public DNS, and
Enabled with just a single click
Then how can I find them?
Means of Exposing
With Funnels issuing certificates and creating DNS records, the breadcrumbs are already out there, I just need to follow them.
The plan is pretty simple:
Scrape *.ts.net domains from a certificate transparency log
Resolve the domains using a public DNS to find live nodes
Probe the live nodes to see if they’re hosting something
I’m not brute-forcing. I’m not scanning. Simply connecting dots already visible to anyone who looks.
To make this magic happen, I used:
crt.sh to query certificate transparency logs
A local database to track results
A short script to scrape and resolve domains
gowitness for headless screenshots, titles, status codes, and more
The goal here isn't to analyze the entire dataset — not yet. I just need a large enough sample to start noticing patterns. In this case, about a month’s worth of certificates was more than enough.
Step 1: Querying Funnels

Just to fast-forward through some trial and error: *.ts.net domains are too plentiful for the crt.sh web interface to handle gracefully. In fact, results are truncated to ~2000 queries, many of which are dupes.
To sidestep this, I constructed a PostgreSQL query and queried their database directly.
Here’s my script:
/code
WITH ci AS (
SELET sub.CERTIFICATE_ID,
array_agg(DISTINCT sub.NAME_VALUE) AS NAME_VALUES
FROM (
SELECT cai.*
FROM certificate_and_identities cai
WHERE plainto_tsquery('certwatch', 'ts.net') @@ identities(cai.CERTIFICATE)
AND cai.NAME_VALUE LIKE '%.ts.net'
AND cai.NAME_TYPE = '2.5.4.3'
AND cai.CERTIFICATE_ID BETWEEN {start} AND {end}
LIMIT {QUERY_LIMIT}
) sub
GROUP BY sub.CERTIFICATE_ID
)
SELECT DISTINCT unnest(ci.NAME_VALUES) AS domain, ci.CERTIFICATE_ID FROM ci;This query does a few things: filtering .ts.net domains, filtering out duplicates, and gracefully avoiding timeouts by querying in Certificate ID batches.
I made the assumption that, since certificate transparency logs are append-only, their subtables are implicitly sorted by Certificate ID. This means I’d be able to choose distinct time-frames by simply adjusting the CERTIFICATE_ID range. No need to wrestle with sorting timestamps or full log scans. In practice, this allowed me to define a moving window, scraping in chunks of 50 million IDs at a time. This range was wide enough where crt.sh would not timeout the request or truncate the results.
Once this pipeline was functioning, I tossed all my results in a PostgreSQL table, storing the highest-seen Certificate ID as a baseline-reference for any future continuations.
By grabbing all the relevant domains from the past month (about 100k from April 2025 - May 2025), I was ready to resolve!
Step 2: Resolving Funnels
With our domains in hand, our next step is to figure out which ones are actually alive. Given that Funnels issue real DNS records, all I had to do is ask! Luckily for us, this is as trivial as it sounds.
I did not do anything too fancy here: just used Python’s asyncio tuned with maximum concurrency settings to hammer through our list. Since this was all running on an AWS box with decent network throughput, I could afford to be aggressive.
I did not bother specifying resolvers either, just leaned on whatever default DNS the box had baked in. It was probably AWS’s internal DNS, but I didn’t really care. The goal is volume, not fidelity.
If the domain resolves to an IP, then I can note that it exists!
And some certainly did (about 3500).
Step 3: Probing at Scale
In order to actually see what these exposed domains were serving, I turned to gowitness. This headless browser lets you snap screenshots, grab titles, pull status codes, and collect headers. All without interacting directly.
Once I configured gowitness with timeout and retry mechanisms that worked, I began peeking at each resolved domain. Some endpoints no longer existed — resulting in error codes or redirecting to nothing. Others? Dashboards, login panels, open cameras, in-progress projects, self-hosted sites, and more.
There’s a whole world out there that might believe a random hex string is enough to stay hidden. But with certificate transparency logs, resolvable DNS, and a few lines of Python, that illusion crumbles fast.
So what did I actually find?
Our Findings
From our original pool of ~100,000 .ts.net domains, ~3500 resolved to valid Funnels. Many of them presented login portals — largely expected and benign. However, several didn’t. Here’s a small, anonymized sampling of what appeared without scanning, without password guessing, and without exploitation. Just screenshots from publicly exposed Tailscale nodes. Below are four of the more surprising (and illustrative) examples found in this scrape.
Snapshot Highlights
Open BitTorrent Interface

A publicly exposed BitTorrent client is actively leeching and seeding with internal-style names like “WoodmanCastingX…” and “Private.18.06.21.Elie.Rose.F…”. Whether it is simply personal media (likely story) or adult content, it clearly was not intended for public access. Furthermore, the 5.8TB of free disk space on the device suggests the node is likely always online. Upon further investigation, it would appear this site has been patched with a login page, making it a case study in exposure decay, where a misconfiguration was quietly patched sometime after being publicly indexed.
Tsk tsk! Someone’s torrent box is showing.
Unauthenticated Subtitle Manager

This one, though less exciting, places us directly in view of an exposed API key. The key is redacted for privacy, though at the time of capture, it was exposed in plaintext and unauthenticated. The service in this case is Bazarr, a companion app for subtitle management in media setups (like Plex, Sonarr, and Radarr). It just pulls subtitles. With this access, sensitive metadata can be revealed and the API can be abused.
Exposed Webcam Feed

One Funnel-enabled domain was serving a live indoor video feed. No authentication, no warning. Just a direct view of someone’s guinea pig, and the room around it.
Harmless? Maybe. However, a live view into someone's living room crosses a boundary or two. This is clearly not some public stream. Yet through Funnels, it might as well be.
Exposed Index Directory

With an admin/ directory, multiple CRM-based zips, and random dev directories, the potential for abuse on this one is high. In fact, with further investigation, this is literally just a home directory. And not the home directory of some hobbyist but likely of a developer or some internal team machine, likely Funnel-enabled by accident.
This seems too easy, let’s see some stats.
Implications
What's shown up above is not the status quo. Most Tailscale users are careful. Most services are either dead, login-gated, or so boring they don’t make the cut.
But that’s the point. The weird stuff doesn’t happen because the people are reckless. It happens because Funnels make exposure frictionless. Remember: Just one checkbox between the world and your guinea pig cam.
To better understand typical exposure types, I broke the results down into four focused digests.
Status Codes
This shows how many Funnel endpoints are serving fully functional web pages. It’s a basic measure of reliability.
Status Code | Meaning | Frequency |
200 | OK | 2934 |
404 | Not Found | 315 |
401 | Unauthorized | 193 |
403 | Forbidden | 36 |
500 | Server Error | 11 |
400 | Bad Request | 11 |
503 | Service Unavailable | 4 |
502 | Bad Gateway | 4 |
405 | Method Not Allowed | 3 |
From this, I gathered that 2934 out of 3511 were functioning. This is about 84%. And honestly? That's fairly high! Through the ephemeral nature of Funnels, at any given moment, I found that 84% of resolvable domains are valid, suggesting high reliability, though this should be confirmed long-term.
Most Common Services
Aggregated from page titles, this reveals what users most frequently expose. This is a peek at the most popular tools behind Funnels.
Service | Frequency |
Home Assistant | 673 |
Jellyfin | 202 |
n8n.io - Workflow Automation | 84 |
Sign In - Overseerr | 80 |
BlueBubbles Server | 71 |
Login – Nextcloud | 69 |
Vaultwarden Web | 53 |
Login - Immich | 46 |
Open WebUI | 43 |
Audiobookshelf | 40 |
By an overwhelming margin, Home Assistant takes up about 19% of all funnels. This is extremely interesting, given that Home Assistant allows for remote control and configuration of home IoT and automation. Though login is required, there is reason for concern given both the nature of Funnels and Home Assistant’s broad capabilities.
Most Common Insecure Configurations
These are services that return default pages. This suggests a clear signal of misconfiguration or neglect.
Title | Frequency |
Welcome to nginx! | 26 |
Index of / | 9 |
Apache2 Debian Default Page: It works | 6 |
Apache2 Ubuntu Default Page: It works | 6 |
Directory listing for / | 6 |
Default Site | 2 |
Index of /images/ | 1 |
Welcome to nginx!!! | 1 |
Test Page for the HTTP Page | 1 |
Most of these are not inherently dangerous, especially the Nginx and Apache defaults. But, they often mark the edges of misconfiguration. When someone exposes a Funnel and forgets about it, it is often these default pages that slip through.
Worst still, and somewhat alarmingly, some of these pages link to straight up directories, allowing anyone with the link to peruse the content of that directory or even machine. All visible, indexable, and open. With the URL randomization Funnels provide, this behavior could be deliberate, enabling quick, ephemeral access to personal or adversarial assets.
Subdomain-to-Title Transparency
Lastly, this measures how much the subdomain leaks about the service it’s exposing. Basically, how often a certificate’s listed domain is a dead giveaway.
I was able to calculate transparency by parsing strings from the title, and fuzzy matching them to the subdomain.
For instance:
A subdomain like homeassistant.cat-crocodile.ts.net with title Home Assistant would have a very high similarity.
A subdomain like raspi.cat-crocodile.ts.net with title “Index Of /” would have very low similarity.
Since the list is substantial, I’ll share the digest:
40% of all domains showed 67% or higher subdomain-to-title similarity, suggesting a large portion of subdomains are highly suggestive of the services they host.
24% of all domains showed 100% subdomain-to-title similarity, suggesting a substantial portion are directly transparent to the services they host.
This, though it may seem minor, is critical, as it suggests many Funnel users are deliberate in their subdomain naming and unintentionally advertise what’s running behind them.
Next Steps: Turning Curiosity Offensive
So far, I’ve just scratched the surface, with a mostly passive approach. However, this dataset and the opportunity offered lends itself to much more than passive analysis. If misconfigurations in Funnels or their services can be so exposed, even for a moment, then every second counts. Especially before any mistake is recognized and patched.
If the breadcrumbs are public, then so is the opportunity.
Were someone inclined to misuse the opportunity, a few obvious next steps emerge:
Automated Crawlers
Rather than just scratching the surface, the natural next step would be to use a full spider. With a priority shift to maximum surface coverage, useful resources like static pages, linked assets, API endpoints, and embedded secrets could be extracted and indexed rapidly.
Exposure Severity Triage
Not all exposures are created equal. Some endpoints are login panels, while others are unauthenticated dashboards. By fingerprinting benign services, an attacker could triage severity and prioritize targets using basic heuristics. This increases the likelihood of finding vulnerable targets while misconfigurations still exist
Real-Time Funnel Monitoring
Funnels issue certificates as soon as a user enables public access. Those same certificates hit transparency logs almost instantly. In that instant, the opportunity for abuse is most ripe. Through this just-in-time targeting, fresh Funnels could be attacked before the site owner even realizes the severity of their exposure.
A Final Word
This whole issue is not unique to Tailscale. In fact, misconfigurations are frequent even in traditional VPN architectures, port-forwards, and networks in general. Mistakes are timeless.
What is interesting here is how easy it is to make this mistake.
Tailscale offers frictionless connectivity and it does its job phenomenally — but that same frictionless ease cuts both ways.
Users may not realize it, but the exposure is real. And so are the consequences.




Comments