Blog freshness: Research notes liveLatest update: May 2026Telemetry mode: Public-safe live stripAI tools: Self-hosted demos live
Skip to main content
General
July 11, 2025
8 min read

🧱 Home Server Chronicles: My Docker-Powered Ecosystem — Part 2

Deep dive into the backbone of secure external access: Nginx Proxy Manager, Authentik SSO/2FA, and Tailscale VPN — the core services that make everything work.

Words

1,489

Read Time

8 min read

Category

General

Read aloud
Browser TTS unavailable
Ready for a more natural read-aloud pass.
Reading list
Reading History

Recent articles you open here will appear in this quick history.

#Docker#Self-Hosting#Nginx#Authentik#Tailscale#VPN+3

The Security Foundation

Welcome to Part 2 of my Home Server Chronicles! In Part 1, we explored the overall architecture and philosophy behind my Docker-powered ecosystem. Now, let's dive into the core services that form the backbone of secure external access.

These three services work together to create a robust, secure gateway to my home lab:

  • Nginx Proxy Manager — Reverse proxy with SSL termination
  • Authentik — SSO/2FA identity provider
  • Tailscale — Mesh VPN for remote access

Nginx Proxy Manager: The Traffic Director

Why NPM Over Traefik?

I chose Nginx Proxy Manager over Traefik for several reasons:

  • Web UI: Clean, intuitive interface for managing proxy hosts
  • Built-in SSL: Automatic Let's Encrypt certificate generation
  • Real-time logs: Easy debugging with live access logs
  • Theme integration: Works seamlessly with theme.park for dark mode
  • Flexibility: Easy custom configurations and advanced routing

Configuration

Here's how NPM is configured in my core.yml:

nginx-proxy-manager:
  image: jc21/nginx-proxy-manager:latest
  container_name: nginx-proxy-manager
  restart: unless-stopped
  ports:
    - "80:80"                  # PUBLIC - HTTP proxy hosts
    - "443:443"                # PUBLIC - HTTPS proxy hosts
    - "127.0.0.1:81:81"        # LOCALHOST - admin interface
    - "${HOST_IP:-10.0.0.101}:81:81"       # LOCAL NETWORK - admin interface
    - "100.89.188.84:81:81"    # TAILSCALE - admin interface
  environment:
    - DISABLE_IPV6=true
    - TP_THEME=dark
    - TP_URL=http://theme-park:80
  volumes:
    - ./nginx-proxy-manager/data:/data
    - ./nginx-proxy-manager/letsencrypt:/etc/letsencrypt
  networks:
    - proxy_net

Key Features in Action

Proxy Hosts: Each service gets its own subdomain:

  • jellyfin.jay739.devjellyfin:8096
  • tools.jay739.devit-tools:80
  • notes.jay739.devpaperless-ngx:8000

SSL Certificates: Automatically managed via Let's Encrypt:

  • Wildcard certificates for *.jay739.dev
  • Auto-renewal every 60 days
  • HTTP to HTTPS redirect enforced

Dark Theme: Integration with theme.park for consistent dark mode across all services.


Authentik: The Identity Provider

Full SSO with Two-Factor Authentication

Authentik is a self-hosted identity provider that acts as the SSO layer across all my services. It replaced Authelia early on because I needed richer features — SAML/OIDC support, user management UI, and application-level access policies. It integrates with Nginx Proxy Manager via forward-auth headers.

Architecture

User Request → NPM → Authentik (if protected) → Target Service

Configuration

In infra.yml, Authentik runs as a server + worker pair backed by its own PostgreSQL and Redis instances:

authentik-server:
  image: ghcr.io/goauthentik/server:latest
  container_name: authentik-server
  restart: unless-stopped
  command: server
  environment:
    - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
    - AUTHENTIK_POSTGRESQL__HOST=authentik-postgres
    - AUTHENTIK_POSTGRESQL__NAME=authentik
    - AUTHENTIK_POSTGRESQL__USER=authentik
    - AUTHENTIK_POSTGRESQL__PASSWORD=${AUTHENTIK_DB_PASSWORD}
    - AUTHENTIK_REDIS__HOST=authentik-redis
  ports:
    - "${HOST_IP:-10.0.0.101}:9001:9000"
    - "100.89.188.84:9001:9000"
  depends_on:
    - authentik-postgres
    - authentik-redis
  networks:
    - proxy_net

authentik-worker:
  image: ghcr.io/goauthentik/server:latest
  container_name: authentik-worker
  restart: unless-stopped
  command: worker
  # ... same env vars as server

What Gets Protected?

I use a tiered approach to security:

High Security (SSO + 2FA Required):

  • Portainer (container management)
  • Nginx Proxy Manager admin
  • Vaultwarden (password manager)
  • Immich (photo library)

Medium Security (Local Network / Tailscale Only):

  • Jellyfin (media streaming)
  • Paperless-NGX (documents)
  • Homarr / Homepage (dashboards)
  • Open WebUI (LLM interface)

Public Access:

  • Portfolio website (jay739.dev)
  • IT-Tools, Vert, Paste (utility tools)

Setup Process

  1. Initial Setup: Admin account created on first boot, users/groups managed via Authentik admin UI
  2. TOTP/WebAuthn: Each user enrolls 2FA devices through the Authentik user dashboard
  3. Application Providers: Each service registered as an Authentik Application with proxy provider
  4. Session Management: Configurable session duration with remember-me support

Tailscale: The Mesh VPN

Why Tailscale?

Tailscale provides a WireGuard-based mesh VPN that connects all my devices without port forwarding or complex configuration:

  • Zero Config: No port forwarding, NAT traversal handled automatically
  • MagicDNS: Automatic DNS for all devices on the tailnet
  • Cross-Platform: Works on macOS, iOS, Android, Linux, Windows
  • WireGuard Under the Hood: State-of-the-art cryptography
  • Exit Nodes: Route traffic through any device on the tailnet

Setup

Tailscale runs natively on the host (not in a container) for best performance:

# Install and authenticate
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --accept-routes --advertise-exit-node

Network Layout

My Tailscale tailnet connects 8 devices across multiple platforms:

Batcave (100.89.188.84)  ←→  Oracle VPS (100.80.153.103)
       ↕                              ↕
MacBook Air (100.103.152.9)    iPhone (100.105.125.76)
Mac Mini (100.104.170.37)      Pixel 7 (100.79.158.25)

The Oracle VPS acts as an exit node, and all services bind to both the LAN IP (10.0.0.101) and Tailscale IP (100.89.188.84) for multi-path access.

Split DNS

I use dnsmasq on Batcave with custom DNS records pointing *.jay739.dev to the LAN IP (10.0.0.101), so devices on the local network can reach services directly without depending on Tailscale. See my Tailscale resilience post for how I eliminated Tailscale as a single point of failure.


Traffic Flow: How It All Works Together

Here's the complete request flow for a protected service:

flowchart TD
    A[User Request] --> B[Nginx Proxy Manager]
    B --> C{Protected Service?}
    C -->|Yes| D[Authentik Authentication]
    C -->|No| E[Direct to Service]
    D --> F{Valid Session?}
    F -->|No| G[Login + 2FA]
    F -->|Yes| E
    G --> H[TOTP/WebAuthn Verification]
    H --> E[Forward to Service]
    E --> I[Service Response]
    I --> J[User]

Example: Accessing Portainer

  1. Request: User navigates to portainer.jay739.dev
  2. NPM: Nginx Proxy Manager receives request
  3. Auth Check: Forward-auth header triggers Authentik
  4. 2FA: User enters username/password + TOTP code
  5. Session: Authentik creates session cookie
  6. Forwarding: Request forwarded to Portainer container
  7. Response: Portainer interface loads

Access Patterns

I've designed three distinct access patterns:

Local Network (10.0.0.101)

  • Usage: When I'm at home
  • Security: Reduced authentication for convenience
  • Services: All services accessible directly

Internet (jay739.dev subdomains)

  • Usage: Public internet access
  • Security: Full 2FA protection for sensitive services
  • Services: Curated list of safe-to-expose services

Tailscale Mesh VPN (100.89.188.84)

  • Usage: Remote work and administration from anywhere
  • Security: WireGuard encryption + Authentik 2FA for maximum security
  • Services: Full access to all services via Tailscale IP bindings

Management & Monitoring

Nginx Proxy Manager Admin

  • URL: https://npm.jay739.dev:81
  • Features:
    • Live access logs
    • SSL certificate management
    • Custom location blocks
    • Fail2ban integration

Authentik Admin

  • URL: https://auth.jay739.dev
  • Features:
    • Full user/group management UI
    • Application provider configuration (proxy, OIDC, SAML)
    • 2FA device enrollment (TOTP, WebAuthn)
    • Flow designer for custom login experiences
    • Event logs and audit trail

Tailscale Admin

  • URL: Tailscale Admin Console
  • Features:
    • Device management across the tailnet
    • ACL policy configuration
    • Exit node management
    • MagicDNS and Split DNS settings

Security Considerations

Defense in Depth

  • Layer 1: Firewall (ufw) blocks unnecessary ports
  • Layer 2: Tailscale mesh VPN for remote access
  • Layer 3: Reverse proxy with rate limiting
  • Layer 4: 2FA authentication for sensitive services
  • Layer 5: Container isolation and least privilege

Fail2ban Integration

# Monitors NPM logs for failed auth attempts
[nginx-proxy-manager]
enabled = true
port = http,https
filter = nginx-proxy-manager
logpath = /var/log/nginx-proxy-manager/*.log
maxretry = 3
bantime = 86400

Regular Security Tasks

  • Monthly SSL certificate renewal checks
  • Quarterly user access reviews
  • Regular backup of configuration files
  • Monitor auth logs for suspicious activity

Benefits of This Stack

For Security

  • Zero Trust: Every request authenticated
  • Encrypted Transit: All traffic over HTTPS/VPN
  • Audit Trail: Complete access logging
  • Isolation: Services can't access each other unnecessarily

For Convenience

  • Single Sign-On: One auth session for all services
  • Custom Domains: Memorable subdomains for each service
  • Mobile Access: Full functionality on phone/tablet
  • Offline Capability: VPN works even when internet is down

For Maintenance

  • Centralized Management: One interface for all proxy configs
  • Automated SSL: Set-and-forget certificate management
  • Easy Troubleshooting: Clear logs and error messages
  • Backup/Restore: Simple configuration file management

What's Next?

In Part 3, we'll explore the Infrastructure Layer — the tools that keep everything running smoothly:

  • Portainer: Container management with a web UI
  • VS Code Server: Cloud-based development environment
  • Netdata: Real-time system monitoring
  • Watchtower: Automated container updates
  • Backup Strategy: Protecting your data

This foundation of core services makes everything else possible. With secure access, authentication, and VPN connectivity in place, we can confidently expose our services to the internet while maintaining tight security controls.


Part 1 ← Introduction & Architecture
Part 3 → Infrastructure Layer

Questions or want to replicate this setup? Feel free to reach out!

— Jayakrishna

Continue Reading

These are close to this article’s reading time, so they make a good next step without a big context switch.