Self-hosting has a famous last mile. Installing an app on your own machine is easy today. Making that app reachable from the Internet, with a real domain name and a valid HTTPS certificate, is where most people give up. You need a public IP (increasingly rare behind CGNAT), the ability and permission to forward ports 80 and 443 on your box, DNS records, and certificate management.
NSL.SH exists to remove exactly that link, and nothing else. This post explains how it works under the hood, what goes through our infrastructure and what does not, and how it compares to other approaches. These are questions we get asked often, and they deserve a clear, public answer.
What NSL.SH is
NSL.SH is an open source mesh routing, domain, and TLS automation layer. When your server runs the local mesh-router, every app you install automatically gets an address of the shape:
{subdomain}.{user-domain}.{server-domain}
For example: app.john.nsl.sh. No DNS configuration, no port forwarding, no certificate renewal cron jobs. Install the app, open the URL.
The architecture: control plane vs data plane
The most important thing to understand about NSL.SH, and the most common source of confusion, is the separation between two planes.
The control plane is naming and verification. When someone opens app.john.nsl.sh, DNS resolution goes through the NSL backend, which verifies the server's identity via a libp2p cryptographic signature, and the local mesh-router on your machine then resolves the subdomain dynamically to the right app container. This control plane always transits our backend. That is centralized, and it is intentional: it is the price of automatic domains and TLS with zero configuration.
The data plane is the actual application traffic, and it is not necessarily centralized. There are two transport modes:
- Direct route. If your server is reachable (public IP, ports open), traffic flows directly between the visitor and your machine. It never transits our gateway.
- Tunnel mode. If your server is not reachable (CGNAT, no public IP, no port forwarding possible), a WireGuard tunnel is set up between two roles we call Provider and Requester, and requests are forwarded through the tunnel endpoint.
So when someone asks "aren't machines exposed through your central server?", the accurate answer is: yes in tunnel mode, no in direct mode. What is always intermediated is the naming. The network path is only intermediated when your connectivity leaves no other option. And in every mode, your data at rest stays on your own machine.
The paths, in one picture
PATH A: sslip.io direct (strict end-to-end TLS)
Visitor ────────────────── HTTPS ──────────────────► Your server
{dashed-public-ip}.sslip.io TLS terminates on
your machine only
PATH B1: managed nsl.sh domain, direct mode (your server is reachable)
Visitor ── HTTPS ──► Cloudflare Worker ── HTTPS ──► Your server
(public Internet, (NSL edge,
Let's Encrypt re-terminates TLS;
certificate) custom cert on the
edge-to-PCS hop)
PATH B2: managed nsl.sh domain, tunnel mode (CGNAT, no public IP)
Visitor ── HTTPS ──► Cloudflare Worker ── HTTPS ──► nsl.sh gateway ──► Your server
(public Internet, (NSL edge, (WireGuard tunnel
Let's Encrypt re-terminates TLS) forwards the requests
certificate) to your server)
PATH C: your own domain via Cloudflare
Visitor ── HTTPS ──► Cloudflare edge ── HTTPS (Full / Full strict) ──► Your server
your-domain.tld origin link via your
(proxy mode) sslip.io address
TLS: three modes, three different guarantees
We want to be precise here, because "your traffic is encrypted" can mean several different things. Depending on how your server is addressed, the TLS termination point differs:
| Mode | Address shape | Where TLS terminates | Strictly end-to-end to your server? |
| sslip.io direct | {dashed-ip}.sslip.io | On your server itself | Yes. Nothing in the middle can decrypt. |
| nsl.sh managed | app.you.nsl.sh | Re-terminated at the NSL edge (custom certificate on the server-to-edge hop, Let's Encrypt facing the public Internet) | No. The edge sees decrypted traffic in transit. |
| Custom domain via Cloudflare | your own domain, CNAME to your sslip.io address | At the Cloudflare edge (origin link in Full or Full strict) | No. The Cloudflare edge sees decrypted traffic in transit. |
The strict end-to-end guarantee, in the sense that only your server can ever decrypt, holds only for the sslip.io direct path. For the managed nsl.sh domain and Cloudflare-fronted custom domains, the connection is encrypted on every hop but re-terminated at an edge.
One important nuance: even when TLS is unwrapped at an edge, the connection is never in the clear on the wire. Every hop, from the visitor to the edge and from the edge to your server, is a separate encrypted session. Re-termination means an edge component briefly handles plaintext in memory before re-encrypting; it does not mean your traffic ever travels unencrypted across the Internet. If your threat model requires that no intermediary can ever see plaintext at all, use the direct sslip.io address. If you want the convenience of a memorable domain with automatic HTTPS, the managed path is the trade-off you are making, and we would rather you make it knowingly.
The tunnel is request forwarding, not a VPN
A frequent misunderstanding, worth its own section: the NSL.SH tunnel is not a VPN in the usual sense.
When your server is unreachable from the Internet, the WireGuard tunnel exists for one purpose only: forwarding incoming HTTPS requests from the gateway to your server, and carrying the responses back. That is all it does.
It does not give your server a remote public IP. It does not route your server's outbound traffic. Your machine's other connections (updates, API calls, anything it initiates itself) go out through your normal Internet connection, untouched. This keeps the tunnel lightweight and its scope narrow: the gateway only ever sees traffic addressed to your published apps, never your server's general network activity.
A true VPN mode (giving your server an actual remote IP) is on the roadmap, but it will be a separate, opt-in feature, deliberately kept apart from the request-forwarding tunnel. More on that below.
Bring your own domain
You are not locked into nsl.sh addresses. There are two ways to point your own domain at your server.
Option 1: direct DNS, no third party.
- At your DNS provider (any registrar), create an A record from your domain or subdomain to your server's public IP, for example 161.97.177.252.
- Handle HTTPS certificates on your side (for example Let's Encrypt on the server).
This is the most sovereign option: no intermediary between visitors and your machine, and TLS terminates on your server. It requires a reachable public IP.
Option 2: via Cloudflare.
- Add the domain to Cloudflare and update your registrar's nameservers.
- Create a CNAME from your domain or subdomain to your server's sslip.io address, for example 161-97-177-252.sslip.io.
- Enable proxy mode (orange cloud) so Cloudflare terminates HTTPS and hides your origin IP.
- Set the SSL/TLS mode to Full or Full (strict).
This is the simplest option and hides your home IP, at the cost of Cloudflare's edge sitting in the path (see the TLS table above).
In both cases the sslip.io target and the A record IP are per-server, derived from that machine's public IP (dots become dashes, suffixed with .sslip.io). The values above are examples.
How the nsl.sh tunnel compares to a VPN
Another common way to make an unreachable server reachable is a VPN-based service: your server connects to a VPN that gives it a public IP, and everything is exposed through that IP. It is a solid model, so here is an honest comparison.
Privacy. With a VPN, the provider becomes your network operator: all of your server's traffic, inbound and outbound, transits their infrastructure, though TLS still terminates on your machine. With NSL.SH, the scope is narrower: in direct mode nothing transits our infrastructure on the data plane at all, and in tunnel mode only the requests to your published apps do, never your server's general traffic. Neither model is strictly better; they place trust at different points, and we prefer to document exactly where.
Cost. VPN reachability services are typically paid subscriptions. NSL.SH is free to use, and the code is open source, which means the routing layer itself can be operated by anyone, not only by us. The honest long-term answer to "free forever?" is that the openness of the code is the real guarantee: even if the hosted nsl.sh service changed, the layer would remain deployable independently. And because the tunnel only forwards requests rather than carrying all your traffic, the infrastructure cost per user stays small, which is what makes the free model sustainable.
Complexity. A VPN requires configuring a tunnel client and generally assumes some network literacy. NSL.SH assumes nothing about your network: no public IP, no port forwarding, no router configuration. Behind CGNAT, where port forwarding is simply impossible, tunnel mode works out of the box.
What will change in the future
Two evolutions are planned, both designed to widen the range of trade-offs you can choose from:
- DNS mode. A mode where the NSL control plane is used purely for name resolution, pointing directly at your server, so that even the managed-domain path can avoid edge re-termination when your server is reachable. The goal: the convenience of an nsl.sh name with TLS terminating on your machine.
- VPN mode. An opt-in mode giving your server an actual remote public IP, for use cases that need full inbound exposure beyond HTTPS (arbitrary ports, non-HTTP protocols). It will be built and operated separately from the request-forwarding tunnel, so that choosing one never silently gives you the other.
Why we build it this way
Our priority is to make open source accessible to everyone, including people who will never touch a DNS config or open a port. Yundera and NSL.SH share this same goal; Yundera simply takes the idea further, all the way to a managed personal cloud.
That is our real difference with projects that pursue the state of the art of pure self-hosting, like YunoHost. Those projects give you the full stack, including the network edge, and assume you can solve reachability yourself. We assume the opposite, and we accept deliberate compromises in favor of simplicity. What we owe you in exchange is transparency and consistency about those compromises. That is what this post is for.