Table of Contents
- The Overview
- What Is Tailscale?
- How I Came About Tailscale
- Environment Setup
- Tailscale Installation: First Attempt
- What the Hell Is TLS and Why It Blocked Me
- Why Google Works but Tailscale Fails
- The Pattern and the Hypothesis
- Enter the VPN Just Testing
- Installing Tailscale Adding the Repository
- Installing Tailscale The Real Deal
- Bringing the Device Online
- Device Authorization and Onboarding
- Adding My Windows Machine
- Two Machines One Private Network
- What Is a Tailnet
- Checking the Tailscale Status
- Starting the Web App via Tailscale
- Accessing the App via Tailscale
- So What the Hell Happened
- Why the Second IP Worked
- The Not Secure Warning in the Browser
- Final Notes Scope and Access
The Overview:
In this piece, I will walk through how I exposed a fully built and Dockerized MERN application using Tailscale.
The application itself was already complete, so this article focuses on the infrastructure decision and setup, not application development.
While installing Tailscale, I encountered an unexpected TLS error, despite my internet connection being functional. That debugging journey is what this write-up is really about. Well, and Other issues too.
So, let’s get right into it.
What is Tailscale?
Tailscale is a secure networking tool that enables devices, such as laptops, phones, and servers, to communicate over the internet as if they were on the same private local network.
Instead of exposing your app to the public internet or dealing with complex VPN setups, Tailscale gives each device a private IP address and connects them directly. This means we can run MERN apps or web apps locally or on a private server and still access them from anywhere, without public IPs, port forwarding, or buying a domain.
Now that we have got that out of the way, how on earth did I come about Tailscale?
How I came about Tailscale?
Before December 2025, I had never heard of Tailscale. I stumbled on it through a YouTube video (link below), got curious, and decided to see if it could solve a real problem I was facing: exposing my web app for remote access without using public IPs.
That curiosity led to an unexpected networking issue and a useful learning experience.
Link here:
https://youtube.com/shorts/n8C1SS-Z4L8?si=bS31jYL-NrWPTEdL
Environment Setup:
At this point, the application itself was already complete.
All Docker images had been pulled from Docker Hub, and the project was ready to run.
The setup was simple:
Host Os: Windows
Linux Environment: Ubuntu via WSL2
Container Runtime: Docker Desktop
The web application runs inside the Ubuntu environment located at:
voke@DESKTOP-V2*****:~/homelab-server/deploy$
So normally, if I run:
docker compose up -d
This would bring up all containers successfully. Because the application sits behind Nginx, it would be accessible locally on:
http://localhost/
At this stage, everything worked, but only locally.
To access the application from outside my machine, I needed a secure networking layer. That’s where Tailscale came in.
Tailscale Installation: First Attempt:
So I attempted to install Tailscale using the official install script:
curl -fsSL https://tailscale.com/install.sh | sh
This command downloads and executes Tailscale’s installation script, allowing the tool to be installed without manual configuration.
However, the installation failed with the following error:
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to tailscale.com:443
Damnnn!!! That hit me hard, as I wasn't expecting such. I was expecting a smooth sail, only for me to hit an iceberg like the Titanic. Well, in programming and Development as a whole, anything can happen. And that’s no reference to the Refugee Camp All-Stars and Wyclef Jean's hit song from 1997.
So that error above indicates that curl was unable to establish a secure HTTPS connection to tailscale.com.
In most cases, such an error is caused by either of the following:
Network filtering or blocked HTTPS traffic.
DNS resolution issues
TLS interception or inspection
My first assumption was a network problem, so I verified that my internet connection was working correctly. It was.
What the Hell Is TLS and Why It Blocked Me
At this point, it’s useful to understand what’s actually failing here.
TLS stands for Transport Layer Security, which is what keeps data safe and encrypted when it travels over the internet. It ensures that when a machine connects to a server, the data can’t be read by or altered by anyone in between.
To determine whether this was a general TLS issue or something more specific, I tested another HTTPS endpoint:
curl -I https://google.com
The TLS handshake completed successfully, confirming that HTTPS worked for major domains.
From the response, I could confirm:
TLS 1.3 handshake completed
Valid CA chain,
IPv4 + IPv6 both resolved,
HTTP/2 negotiated.
So this ruled out a general network failure and pointed to selective TLS interference.
Why Google Works but Tailscale Fails
Here’s the thing.
Some networks allow traffic to popular CDNs like Google, while silently blocking or interfering with less common endpoints. This is common with mobile broadband, carrier-grade NATs, and Nigerian ISPs.
So the fact that google.com worked didn’t mean everything was fine. It only meant Google was allowed through.
The Pattern and The Hypothesis:
At this point, the pattern was clear. Connections worked for major CDNs but failed for certain domains. This usually indicates interference somewhere along the network path. So I needed a way to change the network path entirely.
So a question came up in my head:
What if I changed the network path entirely? What would happen?
Oops! Let’s make that two questions.
Enter the VPN: Just Testing:
To change the network path, I used a VPN [Virtual Private Network]. A VPN is a simple way to route traffic through a different geographic and network endpoint. At this stage, I wasn’t “solving” anything yet; I was testing a hypothesis.
And if things worked on VPN, then the network is blocking Tailscale.
I used Proton VPN, which I already had installed on my system.
All I had to do was open the Proton VPN app and log in. I logged in using my email and password. I switched to a server in another country and then ran:
curl ifconfig.me
I ran this command before and after switching servers.
The output changed, confirming that my traffic was now coming from a different public IP and location.
That was all I needed.
Installing Tailscale Adding the Repository
So after changing location, I fired this command in the terminal again:
curl -fsSL https://tailscale.com/install.sh | sh
It succeeded this time around. wheeeeew!!! And here’s what happened:
The script added Tailscale’s official repository for my Ubuntu version (noble).
It imported the signing key.
Ran apt-get update and fetched package lists.
Installed Tailscale packages from https://pkgs.tailscale.com/stable/ubuntu noble/main
No TLS errors. No handshake failures.
At that point, it was clear:
Changing the network path bypassed the interference.
So, it's game on…
Installing Tailscale: The Real Deal:
With the repository in place, I installed Tailscale using the system package manager:
sudo apt-get install tailscale
Running that command prompted my system for a Unix user password, not a Tailscale password, not an email password. But the same password I use to log into my Linux account.
This is required because installing system packages requires root access.
After entering the password and confirming with y, the system will:
Download the Tailscale package
Install the tailscale CLI
Install the tailscaled background service
Register the service with systemd
Enable it to start automatically
Bringing the Device Online:
After installation, I ran this command:
sudo tailscale up
This command authenticates the device and joins it to my private Tailscale network.
Immediately, the terminal printed:
To authenticate, visit:
https://login.tailscale.com/a/1***
I clicked the link and was taken to a sign-in page.
I signed in using my Google account.
Device Authorization and Onboarding
After signing in, I was prompted to connect the device.
Clicked on the connect button, and I saw success in the terminal.
Success
That success message was the green light; the device was now authenticated.
I was then redirected to a short onboarding screen with a form.
So I filled out Tailscale’s onboarding form.
I shared my primary reason for using Tailscale (Application Testing),
My role (Engineer),
and how I heard about Tailscale (YouTube).
Once complete, my desktop was added to my Tailscale network, and I was shown how to add additional devices across Windows, macOS, Linux, and mobile platforms.
Adding My Windows Machine:
On the official downloads page, the Windows section provides a direct link to download the Tailscale installer for Windows 10 or later.
Link here:
https://tailscale.com/download/windows
Downloaded it and ran it.
After running the installer, I got this:
This shows that our installation was successful on Windows. Then I clicked on Get started to log in or sign in. Signed in with my Google account again, and was redirected to my dashboard.
After signing in, we see:
Two Machines, One Private Network:
At this point, we are fully connected to TailScale. Here’s what it means:
I have 2 machines connected to the same tailnet:
desktop-v2***** (Linux / WSL2)
IP: 100.69.232.125
Status: Connected
This is my Ubuntu / WSL environment
desktop-v2***** (Windows 11)
IP: 100.84.202.98
Status: Connected
This is my Windows environment
What this means is:
Tailscale is installed correctly.
Authentication worked
The Nigerian ISP + VPN issues are completely bypassed.
What Is a Tailnet?
O.k. It will be best for me to talk about tailnet, because I mentioned it here:
I have 2 machines connected to the same tailnet:
So, Tailnet is Tailscale’s private network. It’s a collection of devices that you have authenticated using Tailscale. Once a device joins the tailnet, it gets a private IP address and can communicate with other devices on the same network. No public internet is required.
Now that we have trashed that, let’s move on…
Checking the Tailscale Status:
So to see if Tailscale is really working on our machine, I ran this in the terminal:
tailscale status
And I got this response:
100.69.232.125 desktop-v2***** v@ linux -
100.84.202.98 desktop-v2***** v@ windows -
Hell Yeah! This confirmed that both machines were:
connected
authenticated
visible inside the same tailnet
Starting the Web App via Tailscale
Next up, I fired up the MERN application.
Inside my VS Code, I first moved to my home directory by running this:
cd ~
Then I opened the Linux workspace by typing:
code .
From there, I navigated to the deployment directory:
cd deploy
Then I started up the Docker containers:
docker compose up -d
Mind you, my Docker Desktop application was already open with the engine running.
The response from Docker was:
[+] Running 7/7
✔ Network deploy_default Created 0.0s
✔ Container deploy-redis-1 Started 1.3s
✔ Container deploy-mongodb-1 Started 1.3s
✔ Container deploy-server3-1 Started 1.7s
✔ Container deploy-server1-1 Started 1.8s
✔ Container deploy-server2-1 Started 1.7s
✔ Container deploy-reverse-proxy-1 Started
Then I checked to see if all containers were up and running:
docker ps
Got this as an output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5006e66c1b5b vawkei/reverse-proxy:latest "/docker-entrypoint.…" 45 seconds ago Up 42 seconds 0.0.0.0:80->80/tcp deploy-reverse-proxy-1
b976608a5480 vawkei/server2:latest "docker-entrypoint.s…" 45 seconds ago Up 43 seconds 5000/tcp deploy-server2-1
4f1663befaea vawkei/server3:latest "docker-entrypoint.s…" 45 seconds ago Up 43 seconds 5000/tcp deploy-server3-1
fdd6aa247231 vawkei/server1:latest "docker-entrypoint.s…" 45 seconds ago Up 43 seconds 5000/tcp deploy-server1-1
a3f25b1ba739 mongo:8.2 "docker-entrypoint.s…" 46 seconds ago Up 44 seconds 27017/tcp deploy-mongodb-1
6f18d754bba5 redis:6 "docker-entrypoint.s…" 46 seconds ago Up 44 seconds 6379/tcp deploy-redis-1
BRAAAAA!!!
This showed that all containers were working.
Accessing the App via Tailscale:
So at this moment, I was sitting on top of the world like Mase and Brandy in 1998, I tried accessing the web app on this URL:
http://100.69.232.125/
I was all smiles and about popping the champagne bottle, lo and behold, I got hit with this response from my web page.
Your Internet access is blocked
ERR_NETWORK_ACCESS_DENIED
I froze like I got hit with an Ice Ball from Sub-Zero in Mortal Kombat. I was like, whaaaat! It ain't possible. What the hell is this?
This app was literally running. Containers were up. Tailscale was connected. So it wasn't adding up.
Calmed down, took a breather, and walked away to clear my head.
After some hours of thought, I came back to my laptop and tried this instead:
http://100.84.202.98/
Hell Yeah! It worked. I was glad and gave an evil grin.
The web app opened in the browser and was accessible.
So What the Hell Happened?
Let me explain:
I had two Tailscale IPs:
100.69.232.125 desktop-v2***** v@ linux -
100.84.202.98 desktop-v2***** v@ windows -
So when I tried accessing:
http://100.69.232.125/
From Windows, the request path looked like this:
Windows → Tailscale → WSL2 → Docker → Nginx
Windows blocked it before it ever reached Docker.
This wasn’t a Tailscale failure.
This was Windows Defender / firewall guarding cross-interface traffic. which is a common Windows + WSL2 behavior.
Why the Second IP Worked:
When I accessed:
http://100.84.202.98/
I was hitting Windows’ own Tailscale interface.
The App loaded, and the Firewall allowed it.
That worked because:
I was accessing Windows’ own Tailscale interface, and Windows already trusts itself.
This led Traffic to be forwarded internally to WSL2, where Nginx was listening on port 80.
The Not Secure Warning in the Browser
In the picture, you can see Not Secure up there. Even though the browser shows “Not Secure”, all traffic is still encrypted by Tailscale. The warning appears only because the app is served over HTTP and does not use a public TLS certificate. So it’s all good.
Final Notes Scope and Access
One important clarification before wrapping up.
The Tailscale IP used here:
100.84.202.98
It is not a public IP.
It’s a private IP address that exists only inside my tailnet. This means:
- It’s not reachable from the public internet
- It won’t load on random mobile data
- It won’t work without authentication
Only devices that:
- Have Tailscale installed
- Are logged into the same tailnet
- And are actively connected
Can access the app.
And that’s the whole point of this.
This setup was never meant to expose the app publicly. It was meant to securely expose a local, Dockerized MERN app for testing, development, and internal access without domains, port forwarding, or cloud infrastructure.
Thanks for Reading.
I will see you in the next ONE.





Top comments (0)