August 9, 2026
I Built a Zero Trust Network for My Homelab Using NetBird
I self-hosted NetBird and connected my personal devices and servers using identity-based access control, Nginx, Cloudflare, HTTPS, gRPC…
By Jairoy
10 min read
I self-hosted NetBird and connected my personal devices and servers using identity-based access control, Nginx, Cloudflare, HTTPS, gRPC, WebSockets, WireGuard, and least-privilege policies.
I wanted my homelab to behave like a real private network
My homelab isn't a single machine sitting on my desk.
I have a main laptop, a phone, a tablet, multiple VPS instances, and an old laptop that I use for server and lab work.
As the number of machines increased, I started running into a familiar problem:
How do I securely access everything without exposing everything?
Opening SSH ports, remembering IP addresses, configuring individual firewall rules, and relying on public endpoints works for a small setup.
But I wanted something closer to the way I would design an internal network in a real environment.
So I decided to build a self-hosted Zero Trust network using NetBird.
The goal wasn't simply to create a VPN.
The goal was to build a network where access was explicitly defined:
Personal Devices
|
v
NetBird
|
v
Access Policies
|
+---+---+
| |
v v
Servers Old-LaptopPersonal Devices
|
v
NetBird
|
v
Access Policies
|
+---+---+
| |
v v
Servers Old-LaptopThe result became one of the more interesting projects in my homelab because it brought together cybersecurity, networking, Linux administration, reverse proxies, TLS, and DevOps.
The architecture
Architecture overview of my self-hosted Zero Trust homelab. Personal devices connect through NetBird, with access controlled through groups and least-privilege policies.
The environment consists of:
- Main Windows laptop
- Android phone
- Android tablet
- Three VPS instances
- Old laptop/server
- Self-hosted NetBird
- Nginx
- Cloudflare
- Let's Encrypt
- HTTPS/TLS
- gRPC
- WebSockets
- WireGuard
- SSH
- Linux networking
The important part is not the number of technologies.
It's how they fit together.
Why I Built This
The basic requirement was simple:
My personal devices should be able to reach my infrastructure without making that infrastructure broadly accessible.
I wanted to move away from thinking primarily in terms of:
IP address → Port → ServiceIP address → Port → Serviceand instead think in terms of:
Identity → Group → Policy → ResourceIdentity → Group → Policy → ResourceFor example, my intended access model is:
Personal
|
+------> Servers
|
+------> Old-LaptopPersonal
|
+------> Servers
|
+------> Old-LaptopI don't need every server to communicate with every other device.
I don't need my personal devices to automatically have unrestricted access to everything.
And I don't need to create a new collection of firewall rules every time I add another machine.
That is where NetBird became useful.
Why NetBird?
I wanted a system that combined encrypted networking with centralized access control.
NetBird gave me the building blocks I was looking for:
- WireGuard-based connectivity
- Peer management
- Identity-based organization
- Groups
- Access-control policies
- Centralized management
- Server and user separation
- A web dashboard
The important distinction for me was this:
A private network is not automatically a Zero Trust network.
If everything inside the network can communicate with everything else, I have created connectivity, but I haven't necessarily created meaningful segmentation.
I wanted the access itself to be intentional.
Self-Hosting NetBird
Instead of using a hosted control plane, I deployed my own NetBird management infrastructure.
I exposed the management interface through my own domain:
https://netbird.herculesdev.inhttps://netbird.herculesdev.inThe public request path looks approximately like this:
Internet
|
v
Cloudflare
|
v
HTTPS / TLS
|
v
Nginx
|
+--------------------+
| |
v v
NetBird Dashboard NetBird Management
|
+------+------+
| |
gRPC WebSocketsInternet
|
v
Cloudflare
|
v
HTTPS / TLS
|
v
Nginx
|
+--------------------+
| |
v v
NetBird Dashboard NetBird Management
|
+------+------+
| |
gRPC WebSocketsThis immediately made the project more interesting.
I wasn't just installing NetBird on a couple of machines.
I had to make the self-hosted management service work correctly through a reverse proxy and public HTTPS endpoint.
Nginx Was More Important Than I Initially Expected
A normal web application can often be placed behind Nginx with a relatively simple configuration.
NetBird was different.
The deployment needed to handle different types of traffic, including:
- Normal HTTP requests
- API requests
- gRPC
- WebSockets
- Long-lived connections
For example, the Nginx configuration included a WebSocket route similar to:
location ~ ^/(relay|ws-proxy)/ {
proxy_pass http://netbird_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_read_timeout 1d;
}location ~ ^/(relay|ws-proxy)/ {
proxy_pass http://netbird_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_read_timeout 1d;
}And a native gRPC route:
location ~ ^/(signalexchange\.SignalExchange|management\.(ManagementService|ProxyService))/ {
grpc_pass grpc://netbird_server;
grpc_read_timeout 1d;
grpc_send_timeout 1d;
grpc_socket_keepalive on;
}location ~ ^/(signalexchange\.SignalExchange|management\.(ManagementService|ProxyService))/ {
grpc_pass grpc://netbird_server;
grpc_read_timeout 1d;
grpc_send_timeout 1d;
grpc_socket_keepalive on;
}The configuration also included the normal forwarding headers required by the reverse proxy.
This was one of the most useful parts of the project from a DevOps perspective.
It forced me to understand what the application was actually doing instead of treating Nginx as simply:
Internet → Nginx → ApplicationInternet → Nginx → ApplicationHTTPS, Cloudflare and Let's Encrypt
The public NetBird endpoint uses HTTPS.
The infrastructure combines:
Cloudflare
↓
HTTPS
↓
Nginx
↓
NetBirdCloudflare
↓
HTTPS
↓
Nginx
↓
NetBirdLet's Encrypt certificates are used on the server for TLS.
Cloudflare provides the external DNS/proxy layer, while Nginx handles the reverse-proxy side of the deployment.
This also gave me practical experience with an important real-world concept:
TLS termination and application protocols still have to be configured correctly behind the reverse proxy.
HTTPS working in a browser doesn't necessarily mean that every protocol an application uses will work correctly.
That became very obvious during the troubleshooting stage.
The First Problem: 403 Forbidden
My first attempt at connecting the Windows NetBird client failed.
The client displayed:
Login FailedLogin Failedwith an error similar to:
failed getting Management Service public key
rpc error: code = PermissionDenied
unexpected HTTP status code received from server:
403 (Forbidden)
transport: received unexpected content-type "text/html"failed getting Management Service public key
rpc error: code = PermissionDenied
unexpected HTTP status code received from server:
403 (Forbidden)
transport: received unexpected content-type "text/html"This was an interesting failure.
The browser could reach:
https://netbird.herculesdev.inhttps://netbird.herculesdev.inThe dashboard worked.
But the NetBird client was still unable to communicate correctly with the Management Service.
That meant I had to stop thinking about this as an ordinary login problem.
The request path was more like:
NetBird Client
|
v
Cloudflare
|
v
Nginx
|
v
NetBird ManagementNetBird Client
|
v
Cloudflare
|
v
Nginx
|
v
NetBird ManagementAny one of those layers could be responsible.
Debugging the Reverse Proxy
I started checking the active Nginx configuration.
First:
sudo nginx -tsudo nginx -tThen:
sudo nginx -Tsudo nginx -TI also specifically checked that the management and signal-exchange routes were present:
sudo nginx -T 2>/dev/null | \
grep -nE 'signalexchange|management\.(ManagementService|ProxyService)'sudo nginx -T 2>/dev/null | \
grep -nE 'signalexchange|management\.(ManagementService|ProxyService)'The configuration was syntactically valid and the expected routes were present.
This was an important debugging lesson.
A configuration being syntactically correct doesn't necessarily mean that an application will work through it.
You have to verify the actual traffic path.
Looking at the Server Side
I also checked the NetBird management logs.
The logs contained information about instance setup, authentication attempts, and account activity.
For example, there were login-related events such as:
failed login attempt: Invalid credentials.failed login attempt: Invalid credentials.There were also requests coming from the browser to endpoints such as:
/api/users/current
/api/peers
/api/groups
/api/accounts/api/users/current
/api/peers
/api/groups
/api/accountsThis gave me another useful debugging perspective.
Instead of only asking:
"Why isn't the client connecting?"
I started asking:
"Which layer is receiving the request, what response is it returning, and where does the request stop behaving as expected?"
That approach made the problem much easier to isolate.
Getting the Main Laptop Connected
After correcting the configuration and client state, I successfully connected my main laptop.
The NetBird dashboard showed the Windows machine as a peer with a NetBird address in the 100.67.x.x range.
That was the point where the project became a real private network instead of just a server deployment.
Adding My Personal Devices
After the main laptop was working, I added my Android device as another peer.
The goal is to treat these devices as my Personal environment.
The resulting structure is:
Personal
├── Main Laptop
├── Phone
└── TabletPersonal
├── Main Laptop
├── Phone
└── TabletThe dashboard gives each connected peer its own NetBird address.
That means I no longer have to think about the physical network the device happens to be connected to in the same way.
The important identity becomes the NetBird peer.
Groups: Turning Devices Into Security Boundaries
One of the most useful parts of the project was creating logical groups.
I created:
Personal
Servers
Old-LaptopPersonal
Servers
Old-LaptopThe idea is straightforward.
Instead of writing policies around individual machines, I can write policies around groups.
For example:
Personal → ServersPersonal → Serversmeans the policy applies to the Personal group and the Servers group.
If I later add another server, I can place it into the appropriate group instead of redesigning the entire access model.
This is much easier to maintain.
Least-Privilege Access Control
My access model is deliberately small.
I implemented:
Personal → Servers
My personal devices can access my server infrastructure.
Personal → Old-Laptop
My personal devices can access the old laptop.
That's it.
I intentionally did not create a broad policy allowing every group to communicate with every other group.
For example, I don't need:
Servers → ServersServers → Serversjust because the servers happen to be part of the same NetBird environment.
The objective is:
Allow what is required.
Do not automatically allow everything else.Allow what is required.
Do not automatically allow everything else.That is the practical side of least privilege.
The Actual Network
The actual NetBird topology running in my homelab, showing my personal devices, server peers, NetBird addresses, groups, and the relationships between them.
This screenshot is probably the most important visual evidence in the article.
The generated architecture diagram explains the design.
This screenshot shows the actual implementation.
The topology includes my personal devices and server infrastructure, with the NetBird control plane representing the relationships between them.
SSH Over the Private Network
SSH is another important part of the infrastructure.
Instead of treating SSH as a service that should simply be exposed to the public Internet, NetBird gives me another layer between the device and the server.
Conceptually:
Personal Device
|
v
NetBird
|
v
Authorized Server
|
v
SSHPersonal Device
|
v
NetBird
|
v
Authorized Server
|
v
SSHSSH authentication still matters.
NetBird doesn't replace SSH keys.
Instead, the two mechanisms operate at different layers:
NetBird
↓
Controls network connectivity
SSH
↓
Authenticates the user/session
NetBird
↓
Controls network connectivity
SSH
↓
Authenticates the user/session
That separation is useful.
A machine can be reachable through the private network without that automatically meaning that anyone can authenticate to SSH.
The Old Laptop
The old laptop is another part of the homelab.
It is an older HP machine that I use for server and lab purposes.
The planned security model is:
Personal
|
v
NetBird
|
v
Old-LaptopPersonal
|
v
NetBird
|
v
Old-LaptopThe Personal → Old-Laptop policy is already part of the access-control design.
The actual NetBird client deployment on the old laptop is a separate step, so I am keeping that distinction clear rather than claiming the machine is already a connected peer.
That is also one of the reasons I like using groups.
The security policy can be designed before the resource is actually connected.
What I Built
At this point, the project can be summarized as:
INTERNET
|
v
CLOUDFLARE
|
v
HTTPS / TLS
|
v
NGINX
|
+---------+---------+
| |
v v
Dashboard Management
|
+----------+----------+
| |
gRPC WebSockets
| |
+----------+----------+
|
v
NETBIRD NETWORK
|
+--------------+--------------+
| |
v v
Personal Servers
| |
+------+------+ +------+------+------+
| | | | | |
Laptop Phone Tablet VPS 1 VPS 2 VPS 3
Old-LaptopINTERNET
|
v
CLOUDFLARE
|
v
HTTPS / TLS
|
v
NGINX
|
+---------+---------+
| |
v v
Dashboard Management
|
+----------+----------+
| |
gRPC WebSockets
| |
+----------+----------+
|
v
NETBIRD NETWORK
|
+--------------+--------------+
| |
v v
Personal Servers
| |
+------+------+ +------+------+------+
| | | | | |
Laptop Phone Tablet VPS 1 VPS 2 VPS 3
Old-LaptopThe important access relationships are:
Personal → Servers
Personal → Old-LaptopPersonal → Servers
Personal → Old-LaptopRather than unrestricted connectivity between everything.
What Went Wrong Along the Way
The project was useful precisely because it didn't work perfectly on the first attempt.
Reverse proxy problems
The biggest early issue was getting NetBird's different protocols through Nginx correctly.
HTTP working wasn't enough.
The management service also needed the appropriate gRPC routing, while other components required WebSocket support.
403 responses
The Windows client initially received:
403 Forbidden403 Forbiddenand:
unexpected content-type "text/html"unexpected content-type "text/html"That indicated that the client wasn't receiving the response it expected from the Management Service.
Troubleshooting required checking the entire request chain instead of only the client.
Client-side WireGuard state
At one point, the NetBird client reported:
Management: Disconnected
Signal: DisconnectedManagement: Disconnected
Signal: Disconnectedwith:
failed to parse base64-encoded key
illegal base64 datafailed to parse base64-encoded key
illegal base64 dataThis showed that not every connection problem was necessarily caused by Nginx or Cloudflare.
Sometimes the client itself can have a bad state that needs to be reset or re-established.
What I Learned
This project gave me hands-on experience across several areas.
Cybersecurity
I worked with:
- Zero Trust principles
- Least privilege
- Identity-based access
- Network segmentation
- Private service access
- SSH authentication
Networking
I worked with:
- WireGuard
- NetBird
- gRPC
- WebSockets
- HTTPS
- Reverse proxies
- DNS
- Private networking
Linux
I worked directly with:
- Nginx
- systemd
- Linux configuration
- Service logs
- TLS certificates
- Networking
- Shell-based troubleshooting
DevOps
I also had to think about the infrastructure as a system:
DNS
↓
Cloudflare
↓
TLS
↓
Nginx
↓
Application protocols
↓
NetBird Management
↓
ClientsDNS
↓
Cloudflare
↓
TLS
↓
Nginx
↓
Application protocols
↓
NetBird Management
↓
ClientsA failure at any layer can break the final application.
That is something that is much easier to understand after actually debugging it.
Why This Project Matters to Me
The most valuable part of this project wasn't installing NetBird.
It was building the surrounding infrastructure and understanding how the components interact.
A project like this touches several areas that are normally learned separately:
Cybersecurity
+
Networking
+
Linux
+
DevOps
+
Cloud
+
Identity & Access ControlCybersecurity
+
Networking
+
Linux
+
DevOps
+
Cloud
+
Identity & Access ControlInstead of learning each topic independently, I had a reason to connect them.
For example:
Cloudflare isn't just DNS.
Nginx isn't just a web server.
HTTPS isn't just a padlock in the browser.
NetBird isn't just a VPN.
Each component exists as part of a larger architecture.
What's Next?
The next stage is security visibility.
Right now, the network gives me the foundation for controlled private connectivity.
The next question is:
How do I know what's actually happening inside the network?
I want to extend the homelab with stronger monitoring and security visibility.
That means eventually being able to investigate things such as:
- Which devices are connecting?
- Which resources are being accessed?
- Are there unusual connection patterns?
- What authentication events are occurring?
- What activity should trigger an investigation?
- What would happen if one device were compromised?
This is where I want to connect the networking side of the homelab with the cybersecurity monitoring side.
The goal isn't just:
Private NetworkPrivate Networkbut:
Private Network
+
Access Control
+
Monitoring
+
Detection
+
InvestigationPrivate Network
+
Access Control
+
Monitoring
+
Detection
+
InvestigationFinal Thoughts
Building this network changed how I think about my homelab.
Previously, I mostly thought in terms of:
Server IP
Port
SSH
FirewallServer IP
Port
SSH
FirewallNow I'm thinking more in terms of:
Identity
↓
Group
↓
Policy
↓
Encrypted Network
↓
ResourceIdentity
↓
Group
↓
Policy
↓
Encrypted Network
↓
ResourceMy current access model is intentionally simple:
Personal → Servers
Personal → Old-LaptopPersonal → Servers
Personal → Old-LaptopThat simplicity is the point.
I don't want a network where everything can reach everything.
I want a network where access exists because I explicitly allowed it.
The project is still evolving, especially on the monitoring and security-visibility side, but the foundation is now in place:
a self-hosted Zero Trust network connecting my personal devices and homelab infrastructure through NetBird, with Nginx, Cloudflare, HTTPS, gRPC, WebSockets, WireGuard and least-privilege access control working together.
And that's considerably more interesting than simply saying:
"I set up a VPN."
Technologies Used
Networking
- NetBird
- WireGuard
- gRPC
- WebSockets
- Linux networking
Security
- Zero Trust
- Least privilege
- Group-based access control
- SSH keys
- Network segmentation
Infrastructure
- Linux
- Nginx
- Cloudflare
- Let's Encrypt
- HTTPS/TLS
- VPS infrastructure
Tags
#Cybersecurity #Zero_Trust #DevOps #Networking #Homelab