Zero-Trust Networking for Homelabs: Network Isolation and Access Control
Most homelab builders think about security as an afterthought—a firewall rule here, a password there. I made that mistake for years. Then I compromised my entire network because a single Docker container got exploited, and I'd never set up proper network isolation. Zero-trust networking changed everything for me. Instead of trusting anything inside my LAN, I now verify every single connection, segment my network aggressively, and enforce granular access controls. If you're running sensitive services at home—Vaultwarden, Nextcloud, Immich, or anything with personal data—you need this approach.
What Zero-Trust Actually Means in a Homelab
Zero-trust is simple in concept but demanding in practice: never trust, always verify. In enterprise terms, it means no implicit trust based on network location. For us running homelabs, this translates to a few core principles:
- Network segmentation: Separate your devices into security zones. IoT devices don't trust your workstations. Workstations don't trust guest devices. Nobody trusts the Docker containers.
- Microsegmentation: Even within zones, restrict what talks to what. Your Jellyfin server shouldn't be able to reach your Vaultwarden database. Your Ollama container shouldn't reach your network storage.
- Identity and access: Verify who or what is making the request. Not just network-level auth—think of application-level identity using mutual TLS, API keys, or certificate authentication.
- Least privilege: Every service gets the minimum permissions it needs. A web service listening on port 8080 shouldn't have access to your database credentials or SSH keys.
I prefer zero-trust over traditional firewalls alone because a firewall only stops external threats. It won't help if your Jellyfin container gets compromised and an attacker pivots to your file server. With proper segmentation, that pivot fails immediately.
Layer 1: Network Segmentation with VLANs
The foundation of zero-trust networking is splitting your network into security zones. I use VLANs on my managed switch to create logical boundaries. Here's my setup:
- VLAN 10 (Management): Only my workstation and the Proxmox host. Stricter access controls.
- VLAN 20 (Services): Docker containers, VMs running production services. Can't talk to management or storage directly.
- VLAN 30 (Storage): NAS and backup systems. Only services with explicit permission can access.
- VLAN 40 (IoT): Smart devices, printers, cameras. Zero access to anything else.
- VLAN 50 (Guest): Visitors' devices. Can't touch anything.
If you don't have a managed switch, don't skip this—you can still segment using Docker networks and iptables rules on your Linux host. It's messier, but it works.
Layer 2: Docker Network Isolation
For containerized services, Docker networking is your first line of defense. By default, Docker containers on the same bridge network can talk to each other freely. I break that assumption immediately.
version: '3.9'
services:
nextcloud:
image: nextcloud:latest
networks:
- nextcloud-internal
environment:
- MYSQL_HOST=db
ports:
- "8080:80"
db:
image: mariadb:latest
networks:
- nextcloud-internal
environment:
- MYSQL_ROOT_PASSWORD=change_me
volumes:
- db_data:/var/lib/mysql
vaultwarden:
image: vaultwarden/server:latest
networks:
- vaultwarden-internal
ports:
- "8081:80"
volumes:
- vw_data:/data
networks:
nextcloud-internal:
driver: bridge
driver_opts:
com.docker.network.bridge.name: br-nextcloud
vaultwarden-internal:
driver: bridge
driver_opts:
com.docker.network.bridge.name: br-vaultwarden
volumes:
db_data:
vw_data:
Notice: Nextcloud and Vaultwarden run on separate Docker networks. They can't communicate with each other at all. The database only connects to Nextcloud. This is critical—if one service is compromised, the attacker can't immediately pivot to steal credentials from the others.
I also define explicit networks per application stack rather than throwing everything on one "default" bridge. This forces me to be intentional about what talks to what.
Layer 3: Firewall Rules with UFW
Even with network segmentation, I use UFW on my Docker host to enforce additional boundaries. UFW sits above Docker iptables rules and acts as a safety net.
#!/bin/bash
# Enable UFW
sudo ufw --force enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH only from management network (192.168.10.0/24)
sudo ufw allow from 192.168.10.0/24 to any port 22 proto tcp
# Allow Nextcloud only from trusted network
sudo ufw allow from 192.168.10.0/24 to any port 8080 proto tcp
# Allow Jellyfin from services and trusted networks
sudo ufw allow from 192.168.20.0/24 to any port 8096 proto tcp
sudo ufw allow from 192.168.10.0/24 to any port 8096 proto tcp
# Block everything between service networks
# (handled by Docker network isolation, but belt-and-suspenders)
sudo ufw deny from 192.168.20.0/24 to 192.168.30.0/24
# Show rules
sudo ufw status numbered
The magic here is the source-specific rules. Port 8080 is open, but only from my management VLAN. If someone plugs in a laptop on the guest network and scans for open ports, they'll get nothing.
--iptables=false in your Docker daemon config if you want UFW to be the sole firewall, then restart the daemon.Layer 4: Mutual TLS and Application-Level Authentication
Network-level isolation is great, but it's not enough. I layer in mutual TLS (mTLS) for service-to-service communication and require API keys or certificates for data access.
For example, my Nextcloud instance communicates with its database over mTLS. The database server verifies that the client certificate matches what it expects. Even if an attacker gains RCE in the Nextcloud container, they can't connect to the database without the correct certificate—which is mounted read-only and separate from the application code.
I manage certificates using Vault for production deployments and self-signed certificates for homelab services:
# Generate a root CA
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/CN=Homelab-CA"
# Generate server certificate for database
openssl genrsa -out db.key 4096
openssl req -new -key db.key -out db.csr \
-subj "/CN=db.internal"
openssl x509 -req -days 365 -in db.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial -out db.crt
# Generate client certificate for Nextcloud
openssl genrsa -out nextcloud.key 4096
openssl req -new -key nextcloud.key -out nextcloud.csr \
-subj "/CN=nextcloud.internal"
openssl x509 -req -days 365 -in nextcloud.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial -out nextcloud.crt
# Mount in Docker with proper permissions
docker run --rm -v /certs:/certs \
-e MYSQL_SSL_CA=/certs/ca.crt \
-e MYSQL_SSL_CERT=/certs/nextcloud.crt \
-e MYSQL_SSL_KEY=/certs/nextcloud.key \
nextcloud:latest
This way, even if an attacker breaks into my Nextcloud container and finds database credentials in environment variables, they can't connect to the database without the certificate. I store certificates separately from secrets in Docker, and they're read-only within the container.
Layer 5: Monitoring and Anomaly Detection
Zero-trust also means detecting when someone tries to break the rules. I use a combination of tools:
- UFW logging: Enable with
sudo ufw logging on. Monitor/var/log/ufw.logfor blocked traffic. - Docker container metrics: Use Prometheus to scrape network stats and alert on unusual traffic patterns.
- Falco: A runtime security tool that watches system calls. Alerts when containers try to access files they shouldn't or spawn unexpected processes.
- Auditd: Linux kernel auditing. Log all file access, network connections, and privilege escalations.
I don't need enterprise-grade SIEM at home, but I do regularly review logs and set up simple alerts. A container trying to reach a database it's not supposed to know about is an immediate red flag.
Practical Implementation: A Step-by-Step Checklist
Here's how I'd implement zero-trust in an existing homelab without starting from scratch:
- Inventory everything: List all your services, devices, and data flows. What talks to what?
- Create network zones: Set up VLANs (or at least Docker networks) for management, services, storage, IoT, and guest.
- Start with deny-all: Set firewall to drop all traffic, then explicitly allow only what you need.
- Isolate critical data: Your database, file server, and password manager are your crown jewels. Restrict access ruthlessly.
- Use secrets management: Never hardcode credentials. Use Docker secrets, environment variables (stored externally), or Vault.
- Enable logging and monitoring: You can't defend what you can't see.
- Test breakage: Try to ping your storage network from a service container. It should fail. If it succeeds, your segmentation is broken.
A Real-World Example: Protecting Vaultwarden
Vaultwarden holds all my passwords. If it's compromised, everything falls. Here's how I secure it:
- Runs on a dedicated Docker network, isolated from other services.
- Database (PostgreSQL) runs on the same network but is inaccessible from any other container.
- Firewall rules allow access to port 8081 (Vaultwarden web UI) only from my management VLAN.
- Uses mutual TLS for database connections.
- Secrets (database password, encryption key) are stored in a Docker secrets file, not environment variables.
- Reverse proxy (Caddy) sits in front with rate limiting and request logging.
- Auditd monitors file access to the data directory.
Even if an attacker gets RCE in Vaultwarden, they can't steal the database password (it's only available to the database server's certificate), can't pivot to other services (wrong network), and can't exfiltrate data without tripping monitoring alerts.
Addressing the Complexity Trade-Off
Zero-trust is more work than open networks. You'll debug networking issues. Services won't start because they can't reach dependencies. I've spent hours troubleshooting why my backup service couldn't reach the NAS—turns out I'd forgotten to allow traffic between those VLANs.
But that pain is the point. Each failure teaches me something about my network. And the peace of mind knowing that a compromised container can't instantly pivot to my file server is worth every hour spent.
For a homelab, start small. Segment into 2–3 zones, set up Docker network isolation, and use UFW rules. Once that's stable, add mTLS and deeper monitoring. Don't try to deploy full zero-trust overnight.
Next Steps
If you're running services on a VPS and want similar isolation, I'd recommend exploring Tailscale for zero-trust access (replaces traditional VPN), Authelia for centralized authentication, and a reverse proxy like Caddy or Traefik with mTLS support. For scaling beyond a single homelab, platforms like RackNerd's KVM VPS give you the isolation of a full virtual machine—perfect for compartmentalizing environments.
Start your zero-trust journey today. Review your network topology. What's talking to what? What data could leak if one service is compromised? Once you see those attack paths, you'll understand why trust, once given, is hard to revoke.