Zero-Trust Networking for Self-Hosted Services: A Complete Guide
Zero-trust networking with $0/month: Cloudflare Tunnel, Traefik, Authelia SSO, and CrowdSec. Real production configs for self-hosted services.
Zero-Trust Networking for Self-Hosted Services: A Complete Guide
At TechSaaS, we expose 40+ services to the internet from a single host. Gitea, Grafana, BookStack, n8n, code-server, Vaultwarden, Jellyfin — the list goes on. Every one of them is accessible from anywhere. None of them require an open port on our server.
The stack: Cloudflare Tunnel (no inbound ports), Traefik reverse proxy (routing and middleware), Authelia (SSO and access control), and CrowdSec (threat detection). Every piece is either open-source or free-tier. Every config in this article is from our production environment.
Why Zero-Trust Matters for Self-Hosters
Traditional security assumes everything inside the network is trusted. For self-hosters, this model breaks immediately — your "network" is a single server running dozens of services, any of which could be compromised.
Zero-trust means: every request is authenticated, every service is accessed through a controlled gateway, and no traffic reaches a service without passing through multiple security layers. Even if an attacker compromises one service, they can't pivot to others without valid credentials.
Our threat model:
- No inbound ports open to the public internet — eliminates port scanning, direct exploitation, and DDoS to origin
- Every request authenticated before reaching backend services (with explicit exceptions)
- Threat intelligence monitoring all traffic patterns for known attack signatures
Important qualifier: "no open ports" means no inbound ports exposed to the public internet. The host still has internal Docker networking, and several containers mount docker.sock for API access. The zero-trust model protects the external-facing perimeter.
Layer 1: Cloudflare Tunnel — No Open Ports
Cloudflare Tunnel creates an outbound-only connection from our server to Cloudflare's edge network. Traffic flows: User -> Cloudflare CDN/WAF -> Tunnel -> our server. No inbound ports needed. Our origin IP is hidden.
This eliminates entire classes of attacks. Port scanners find nothing. DDoS hits Cloudflare's network, not ours. Exploit scanners can't reach services directly.
Our production config:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate --protocol http2 --retries 15 --grace-period 60s --edge-ip-version auto run
environment:
- TUNNEL_TOKEN=${TUNNEL_TOKEN}
healthcheck:
test: ["CMD", "cloudflared", "tunnel", "ready"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
mem_limit: 128m
cpus: 0.5
cloudflared-replica:
image: cloudflare/cloudflared:latest
container_name: cloudflared-replica
restart: unless-stopped
command: tunnel --no-autoupdate --protocol http2 --retries 15 --grace-period 60s --edge-ip-version auto run
environment:
- TUNNEL_TOKEN=${TUNNEL_TOKEN}
healthcheck:
test: ["CMD", "cloudflared", "tunnel", "ready"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
mem_limit: 128m
cpus: 0.5
We run two tunnel instances for redundancy. Cloudflare load-balances between them automatically. If one crashes, autoheal restarts it while the replica handles traffic seamlessly. The --protocol http2 flag and --retries 15 with --grace-period 60s are tuned for stability on a single host.
The tunnel token is stored as an environment variable, never hardcoded. All DNS routing is configured in the Cloudflare dashboard — each subdomain (git.techsaas.cloud, docs.techsaas.cloud, etc.) routes through the tunnel to Traefik. For the full setup walkthrough, see our Cloudflare Tunnel zero-trust access guide.
Layer 2: Traefik as the Authentication Gateway
Traefik sits behind Cloudflare Tunnel and handles all routing. Its key role in our zero-trust setup is running the ForwardAuth middleware — checking every request with Authelia before forwarding to the backend service.
Get more insights on Security
Join 2,000+ engineers who get our weekly deep-dives. No spam, unsubscribe anytime.
Our actual Traefik middleware config:
# /traefik/dynamic/authelia.yml
http:
middlewares:
https-proto:
headers:
customRequestHeaders:
X-Forwarded-Proto: "https"
authelia:
chain:
middlewares:
- https-proto
- authelia-forward
authelia-forward:
forwardAuth:
address: "http://authelia:9091/api/authz/forward-auth"
trustForwardHeader: true
authResponseHeaders:
- "Remote-User"
- "Remote-Groups"
- "Remote-Email"
- "Remote-Name"
The https-proto fix is critical and underdocumented. When Cloudflare Tunnel terminates TLS and forwards to Traefik over HTTP inside the Docker network, the X-Forwarded-Proto header isn't always set to https. Authelia sees http, deems the connection insecure, refuses to set secure session cookies, and you get an infinite redirect loop. This middleware forces X-Forwarded-Proto: https before the ForwardAuth check.
This took us 4 hours to debug when we first set up the stack. Every Cloudflare Tunnel + Authelia deployment will hit this issue.
The trustForwardHeader: true setting tells Traefik to trust X-Forwarded-* headers from upstream. This is safe in our architecture because Cloudflare is the only ingress path — no traffic can reach Traefik directly from the internet. If anyone could bypass Cloudflare and send traffic straight to Traefik, they could forge these headers and potentially bypass authentication. Architecture matters.
Applying the middleware to services:
# In docker-compose.yml — protected services
n8n:
labels:
- "traefik.http.routers.n8n.middlewares=authelia@file"
code-server:
labels:
- "traefik.http.routers.code-server.middlewares=authelia@file"
grafana:
labels:
- "traefik.http.routers.grafana.middlewares=authelia@file"
Every service that should require authentication gets the authelia@file middleware. Services without it are publicly accessible — which brings us to access control.
Layer 3: Authelia SSO With Tiered Access Control
Authelia acts as the single sign-on gateway. One login protects all services. Access is tiered by user group.
Our authentication backend uses file-based storage with argon2id password hashing:
authentication_backend:
file:
path: /config/users_database.yml
password:
algorithm: argon2id
iterations: 3
memory: 65536
parallelism: 4
salt_length: 16
The access control rules are where zero-trust gets concrete:
access_control:
default_policy: deny
rules:
# Public services — bypass auth (they have their own)
- domain: auth.techsaas.cloud
policy: bypass
- domain: vault.techsaas.cloud # Bitwarden native auth
policy: bypass
- domain: www.techsaas.cloud # Public website
policy: bypass
# Admins — wildcard access to everything
- domain: '*.techsaas.cloud'
subject: 'group:admins'
policy: one_factor
# Senior devs — scoped to dev tools
- domain:
- git.techsaas.cloud
- code.techsaas.cloud
- docs.techsaas.cloud
- pm.techsaas.cloud
subject: 'group:senior-devs'
policy: one_factor
# Junior devs — limited access
- domain:
- git.techsaas.cloud
- docs.techsaas.cloud
- pm.techsaas.cloud
subject: 'group:junior-devs'
policy: one_factor
default_policy: deny is the foundation of zero-trust. If a request doesn't match any rule, it's blocked. No implicit access to anything.
We currently use one_factor (password) for all groups. TOTP is configured (totp: issuer: techsaas.cloud) and Authelia v4.39.x supports WebAuthn (hardware keys), but we haven't enforced two-factor for admin access yet. That's a planned improvement — adding policy: two_factor for sensitive services like Dockge (Docker management) and Grafana (infrastructure visibility).
OIDC: Single Sign-On for Services With Native OAuth
Most zero-trust guides stop at ForwardAuth. We also use Authelia as an OIDC identity provider, giving services like Gitea, BookStack, Vikunja, and Plane native SSO integration:
identity_providers:
oidc:
clients:
- client_id: gitea
- client_id: bookstack
- client_id: vikunja
- client_id: plane
Users sign in once through Authelia and are authenticated across every OIDC-connected service. No separate accounts, no password fatigue, one place to revoke access. We detail the full Authelia setup in our Authelia SSO complete guide.
Gotcha: Authelia sessions expire after 8 hours with a 1-hour inactivity timeout. But OIDC tokens issued to Gitea or BookStack may have different lifetimes. A user can be logged out of Authelia but still have valid OIDC sessions in downstream apps. Align token lifetimes across the stack.
The Bypass List Is Your Biggest Attack Surface
Every service with policy: bypass — Vaultwarden, Ntfy, Conduit, Directus, Postiz, HyperSwitch, Uptime Kuma, Jellyfin, PhotoPrism, and more — relies entirely on its own authentication. We bypass them because their native clients don't support ForwardAuth (mobile apps, desktop clients, API consumers).
We have 15+ bypassed services. Each is a conscious trade-off. If any of these have an authentication vulnerability, Authelia won't catch it. Audit your bypass list quarterly.
Layer 4: CrowdSec — Community Threat Intelligence
CrowdSec analyzes our Traefik access logs and Docker container logs in real-time, matching against community-maintained attack scenarios.
Installed collections:
| Collection | Purpose |
|---|---|
crowdsecurity/traefik |
Parse Traefik access logs |
crowdsecurity/http-cve |
Detect CVE exploitation (Log4Shell, PHPUnit, Confluence, Exchange, FortiOS) |
crowdsecurity/linux |
SSH brute force, system attacks |
crowdsecurity/base-http-scenarios |
Path traversal, SQL injection probes, scanner fingerprinting |
crowdsecurity/sshd |
SSH-specific brute force |
crowdsecurity/whitelist-good-actors |
Don't block legitimate bots (Googlebot, etc.) |
Log ingestion config:
# Traefik access logs
filenames:
- /var/log/traefik/access.log
labels:
type: traefik
---
# Docker container logs
source: docker
container_name_regexp:
- ".*"
labels:
type: docker
Honest disclosure: CrowdSec is currently running in detection mode only. We have CrowdSec v1.7.6 analyzing logs and identifying threats, but we haven't installed the Traefik bouncer plugin that would actively block malicious IPs at the reverse proxy level. CrowdSec without a bouncer is monitoring, not enforcement. We're adding the crowdsecurity/traefik-bouncer to close this gap.
This is a common oversight in self-hosted setups: people install CrowdSec, see the threat detection dashboard, and assume they're protected. Detection without enforcement means you're logging attacks, not stopping them. Learn more in our CrowdSec community-powered intrusion prevention guide.
The Complete Request Flow
Here's what happens when a request hits our infrastructure:
Free Resource
Infrastructure Security Audit Template
The exact audit template we use with clients: 60+ checks across network, identity, secrets management, and compliance.
User
→ Cloudflare CDN/WAF (DDoS protection, caching, TLS termination)
→ Cloudflare Tunnel (encrypted, no open inbound ports)
→ cloudflared container (Docker network)
→ Traefik reverse proxy
├─ Protected services (n8n, Grafana, code-server...):
│ → https-proto middleware (fix X-Forwarded-Proto)
│ → Authelia ForwardAuth check
│ ├─ Not authenticated → redirect to auth.techsaas.cloud
│ └─ Authenticated → inject Remote-User/Groups/Email headers
│ → Backend service
└─ Bypass services (Vaultwarden, www, Ntfy...):
→ Direct to backend service
CrowdSec: monitoring all traffic (detection mode)
Four layers of defense. Cloudflare stops volumetric attacks. The tunnel hides our origin. Traefik + Authelia enforce authentication. CrowdSec watches for attack patterns.
What We Haven't Done (and Why It Matters)
Transparency matters more than looking perfect.
Network segmentation: all 90 containers run on a single flat padc-net bridge network. Every container can talk to every other container. Postgres, Redis, internal APIs — no network-level isolation. This is a known gap. We accept it for operational simplicity (one network = simple DNS, simple debugging), but it means a compromised service can reach everything else. If you're setting up from scratch, use separate networks for databases, monitoring, and application tiers.
Two-factor enforcement: configured but not required. Our Authelia setup supports TOTP and WebAuthn, but all access policies use one_factor. Admin-level services should require two_factor — it's on our roadmap.
CrowdSec enforcement: detection without blocking. The bouncer installation is a 15-minute task we haven't prioritized. Until it's active, CrowdSec is generating logs and threat data that nobody acts on in real time.
File-based auth backend: works for our team size (under 10), but file-based authentication doesn't scale. For larger teams, Authelia supports LDAP backends. Also worth noting: argon2id with 64 MB memory per hash means each login attempt uses 64 MB of RAM. At scale, login attempts could become a resource concern.
The Stack at a Glance
| Layer | Tool | Role | Open Source? |
|---|---|---|---|
| Edge | Cloudflare | CDN, WAF, DDoS, DNS | Free tier (proprietary) |
| Tunnel | cloudflared | Encrypted ingress, no open ports | Source-available |
| Proxy | Traefik | Routing, middleware, ForwardAuth | Yes (Apache 2.0) |
| Auth | Authelia | SSO, access control, OIDC | Yes (Apache 2.0) |
| Detection | CrowdSec | Threat intelligence, log analysis | Yes (MIT) |
Total cost: $0/month. Cloudflare's free tier includes Tunnel, CDN, and basic WAF. Everything else is self-hosted open-source.
Is it equivalent to a $500/month enterprise zero-trust solution like Zscaler or Cloudflare Access (paid tier)? Not entirely — we don't have device posture checks, CASB integration, or per-app risk scoring. But for protecting self-hosted services with strong authentication and hidden infrastructure, this stack covers the critical 90%.
Every config in this article is from our live production environment. Adapt it to yours.
Related reading:
- Securing Self-Hosted Services: A Layered Defense with CrowdSec and Authelia
- Traefik v3 Reverse Proxy: Complete Setup Guide with Docker, SSL, and Auth
- Zero Trust in 2026: From Security Framework to Default Configuration
Building a zero-trust stack for your infrastructure? Explore our cybersecurity and compliance services or cloud infrastructure and DevOps consulting.
Related Service
Security & Compliance
Zero-trust architecture, compliance automation, and incident response planning.
Need help with security?
TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.
We Will Build You a Demo Site — For Free
Like it? Pay us. Do not like it? Walk away, zero complaints. You will spend way less than hiring developers or any agency.
No spam. No contracts. Just a free demo.