๐ŸŽฏ New! Master certifications with Performance-Based Questions (PBQ) โ€” realistic hands-on practice for CompTIA & Cisco exams!

Nginx Proxy Manager Complete Guide 2026: Reverse Proxy, SSL & Home Lab Setup

Published on January 12, 2026


1. Introduction

1.1 What is Nginx Proxy Manager?

Nginx Proxy Manager (NPM) is an open-source, Docker-based reverse proxy management system that provides a beautiful, user-friendly web interface for managing Nginx proxy hosts. Built on top of the battle-tested Nginx web server, it eliminates the need for manual configuration file editing while offering powerful features that would typically require deep Nginx expertise.

[!IMPORTANT] NPM is not a standalone Nginx installationโ€”itโ€™s a pre-configured Docker image built on Nginx, Node.js, and Certbot. It does not support direct installation outside Docker. Always deploy using Docker or Docker Compose.

Key Features:

  • Intuitive Web Interface: Secure admin dashboard based on the Tabler UI framework
  • Free SSL Certificates: Automatic Letโ€™s Encrypt certificate provisioning and renewal
  • Wildcard SSL Support: DNS challenge validation for wildcard certificates
  • Access Control: IP-based restrictions, HTTP Basic Authentication, and access lists
  • Multi-User Support: Role-based user management with audit logs
  • Stream Proxying: TCP/UDP forwarding for non-HTTP services (gaming servers, databases)
  • Custom Configurations: Advanced Nginx directives for experienced users
  • Docker-First Design: Easy deployment with minimal dependencies
  • Database Options: Uses SQLite by default, supports MySQL/MariaDB/PostgreSQL for scalability
  • Security Focus: Runs services as non-root by default in recent versions

1.2 Current Version Information (January 2026)

VersionRelease DateKey Changes
2.13.5November 2025Latest stable, Certbot plugin updates (Glesys), Docker 28 compatibility
2.12.6December 2024Bug fixes, improved Letโ€™s Encrypt handling
2.11.xMid 2024PostgreSQL support, UI improvements
2.10.xEarly 2024Stream host enhancements

[!NOTE] This guide is validated against Nginx Proxy Manager v2.13.5. Docker images are available for amd64, arm64, and armv7 architectures.

1.3 Why Use Nginx Proxy Manager?

BenefitDescription
Visual ConfigurationNo need to edit Nginx config files manually
Time SavingsSet up reverse proxies in minutes, not hours
SSL AutomationFree certificates without complex ACME client setup
Centralized ManagementSingle dashboard for all your proxy hosts
Home Lab FriendlyPerfect for self-hosted applications
Learning ToolUnderstand reverse proxies without the complexity

1.4 NPM vs Alternatives: When to Choose What

RequirementNginx Proxy ManagerTraefikCaddy
Web UI Configurationโœ… Excellentโš ๏ธ Read-onlyโŒ None
Ease of Setupโœ… Beginner-friendlyโš ๏ธ Steeper curveโœ… Easy
Infrastructure as Codeโš ๏ธ Limitedโœ… Excellentโœ… Good
Docker Integrationโœ… Goodโœ… Native labelsโœ… Good
Kubernetes SupportโŒ Noโœ… Nativeโš ๏ธ Limited
Database Requiredโš ๏ธ SQLite/MySQL/PostgreSQLโŒ NoneโŒ None
Observabilityโš ๏ธ Basicโœ… Prometheus metricsโš ๏ธ Limited
Best ForHome labs, beginnersProduction, GitOpsSimple deployments

[!TIP] Choose NPM if you prefer visual configuration and are managing services on a single Docker host. Choose Traefik if you need infrastructure-as-code, GitOps workflows, or Kubernetes.

1.5 What This Guide Covers

This comprehensive guide walks you through:

  1. System Requirements โ€” Hardware and software prerequisites
  2. Installation Methods โ€” Docker Compose, Portainer, NAS devices, Raspberry Pi
  3. Basic Configuration โ€” Creating proxy hosts, redirections, and 404 hosts
  4. SSL Certificate Management โ€” Letโ€™s Encrypt, DNS challenges, wildcards, custom certs
  5. Home Lab Use Cases โ€” Jellyfin, Nextcloud, Plex, Home Assistant, and more
  6. External Access โ€” Cloudflare Tunnels, DDNS, port forwarding
  7. Security Hardening โ€” fail2ban, security headers, access lists
  8. SSO Integration โ€” Authelia and Authentik for single sign-on
  9. Advanced Features โ€” Stream hosts, custom Nginx configs, API usage
  10. Maintenance & Troubleshooting โ€” Backup, updates, common issues

2. System Requirements

2.1 Hardware Requirements

Minimum Requirements (Personal Use)

ComponentMinimumRecommended
CPU1 core (x86_64/ARM)2+ cores
RAM512 MB1 GB
Storage1 GB5 GB (for logs/certs)
Network10 Mbps100+ Mbps

Requirements by Deployment Scale

ScaleUsersServicesRAMCPU
Home Lab1-55-15512 MB - 1 GB1 core
Small Business5-2015-501-2 GB2 cores
Production20+50+2-4 GB4+ cores

[!IMPORTANT] NPM itself is lightweight. Most resources are consumed by the services youโ€™re proxying, not NPM.

2.2 Supported Platforms

Docker Host Operating Systems

Linux (Recommended for Production)

  • Debian 11+, Ubuntu 22.04+
  • Fedora 38+, Rocky Linux 9+
  • Alpine Linux 3.18+
  • Raspberry Pi OS (64-bit recommended)

Windows

  • Windows 10/11 Pro/Enterprise/Education
  • Windows Server 2019/2022
  • Requires Docker Desktop with WSL2 backend

macOS

  • macOS 12 (Monterey) or later
  • Requires Docker Desktop
  • Apple Silicon (M1/M2/M3/M4) fully supported

NAS Devices

  • Synology DSM 7+ (Container Manager/Docker)
  • TrueNAS Scale (native Docker apps)
  • UnRAID 6.10+ (Community Applications)
  • QNAP QTS 5+ (Container Station)

2.3 Network Requirements

PortProtocolPurposeRequired
80TCPHTTP traffic (public)Yes
443TCPHTTPS traffic (public)Yes
81TCPNPM Admin InterfaceInternal only
CustomTCP/UDPStream hostsOptional

[!WARNING] Never expose port 81 to the public internet. The admin interface should only be accessible from your local network or via VPN. Secure it by proxying it through NPM itself with authentication.

2.4 Default Credentials

After first installation, use these credentials to log in:

FieldDefault Value
Emailadmin@example.com
Passwordchangeme

[!CAUTION] You will be forced to change these credentials on first login. Choose a strong, unique password.


3. Installation Methods

3.1 Docker Compose Installation (All Platforms)

This is the recommended installation method for most users. It provides a clean, reproducible setup.

3.1.1 Prerequisites

Ensure Docker and Docker Compose are installed:

Linux (Debian/Ubuntu)

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install Docker using the official script
curl -fsSL https://get.docker.com | sudo sh

# Add your user to the docker group
sudo usermod -aG docker $USER

# Apply group changes (or log out and back in)
newgrp docker

# Verify installation
docker --version
docker compose version

Windows (PowerShell as Administrator)

# Enable WSL2
wsl --install

# Restart your computer, then install Docker Desktop from:
# https://www.docker.com/products/docker-desktop

# Verify installation (after Docker Desktop is running)
docker --version
docker compose version

macOS (Terminal)

# Install via Homebrew
brew install --cask docker

# Or download from https://www.docker.com/products/docker-desktop

# Launch Docker Desktop, then verify
docker --version
docker compose version

3.1.2 Basic Setup with SQLite (Simplest)

This configuration uses SQLite database (built-in), requiring no external database container.

Step 1: Create Project Directory

Linux/macOS:

# Create directory structure
mkdir -p ~/npm-docker/{data,letsencrypt}
cd ~/npm-docker

Windows (PowerShell):

# Create directory structure
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\npm-dockerdata"
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\npm-dockerletsencrypt"
Set-Location "$env:USERPROFILE\npm-docker"

Step 2: Create docker-compose.yml

Create a file named docker-compose.yml:

services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      # Public Ports
      - '80:80'       # HTTP
      - '443:443'     # HTTPS
      # Admin Interface (keep internal only!)
      - '81:81'
    environment:
      # Set your timezone (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
      TZ: "America/New_York"
      # Uncomment if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    healthcheck:
      test: ["CMD", "/usr/bin/check-health"]
      interval: 10s
      timeout: 3s

Step 3: Start the Container

All platforms:

# Pull the image and start the container
docker compose up -d

# View logs to monitor startup
docker compose logs -f

Step 4: Access the Admin Interface

  1. Open your browser and navigate to: http://YOUR_SERVER_IP:81
  2. Log in with default credentials: admin@example.com / changeme
  3. Immediately change your email and password when prompted

For better performance and reliability with many proxy hosts, use MariaDB.

services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
      - '81:81'
    environment:
      TZ: "America/New_York"
      # MariaDB connection
      DB_MYSQL_HOST: "db"
      DB_MYSQL_PORT: 3306
      DB_MYSQL_USER: "npm"
      DB_MYSQL_PASSWORD: "YOUR_STRONG_PASSWORD"
      DB_MYSQL_NAME: "npm"
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    depends_on:
      - db

  db:
    image: 'jc21/mariadb-aria:latest'
    container_name: npm-db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: 'YOUR_ROOT_PASSWORD'
      MYSQL_DATABASE: 'npm'
      MYSQL_USER: 'npm'
      MYSQL_PASSWORD: 'YOUR_STRONG_PASSWORD'
      MARIADB_AUTO_UPGRADE: '1'
    volumes:
      - ./mysql:/var/lib/mysql

[!IMPORTANT] Replace YOUR_STRONG_PASSWORD and YOUR_ROOT_PASSWORD with secure, unique passwords. Generate with:

openssl rand -base64 24

3.1.4 Setup with PostgreSQL

PostgreSQL is also supported (v2.11+):

services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
      - '81:81'
    environment:
      TZ: "America/New_York"
      # PostgreSQL connection
      DB_POSTGRES_HOST: 'db'
      DB_POSTGRES_PORT: '5432'
      DB_POSTGRES_USER: 'npm'
      DB_POSTGRES_PASSWORD: 'YOUR_STRONG_PASSWORD'
      DB_POSTGRES_NAME: 'npm'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    depends_on:
      - db

  db:
    image: postgres:17
    container_name: npm-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: 'npm'
      POSTGRES_PASSWORD: 'YOUR_STRONG_PASSWORD'
      POSTGRES_DB: 'npm'
    volumes:
      - ./postgresql:/var/lib/postgresql/data

3.2 Raspberry Pi / ARM Installation

NPM Docker images support arm64 and armv7 architectures natively. No special configuration needed.

Step 1: Install Docker on Raspberry Pi OS

# Update system
sudo apt update && sudo apt upgrade -y

# Install Docker
curl -fsSL https://get.docker.com | sudo sh

# Add user to docker group
sudo usermod -aG docker $USER

# Reboot to apply changes
sudo reboot

Step 2: Deploy NPM

Use the same docker-compose.yml from Section 3.1.2 (SQLite) or 3.1.3 (MariaDB).

[!TIP] For Raspberry Pi with MariaDB, if you encounter issues with jc21/mariadb-aria:latest, use yobasystems/alpine-mariadb:latest instead:

db:
  image: 'yobasystems/alpine-mariadb:latest'

Optional: Resource Limits for Low-Memory Devices

For Raspberry Pi or other resource-constrained devices, add memory limits:

services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    mem_limit: 512m
    # ... rest of config

Step 3: Verify Architecture

# Check the running container architecture
docker inspect nginx-proxy-manager | grep Architecture

3.3 Synology NAS Installation

Synology DSM 7+ uses Container Manager (Docker) for container deployment.

3.3.1 Port Conflict Resolution

Synologyโ€™s built-in reverse proxy uses ports 80 and 443. You have two options:

Option A: Use Macvlan Network (Recommended)

This gives NPM its own IP address on your network.

  1. Enable SSH on your Synology (Control Panel โ†’ Terminal & SNMP)

  2. Gather Network Info:

    • Your Synology IP (e.g., 192.168.1.100)
    • Subnet mask (e.g., 255.255.255.0 = /24)
    • Network interface name: eth0 or ovs_eth0
  3. Create Macvlan Network (via SSH):

# Replace values with your network configuration
sudo docker network create 
  --driver=macvlan 
  --gateway=192.168.1.1 
  --subnet=192.168.1.0/24 
  --ip-range=192.168.1.200/29 
  -o parent=eth0 
  npm_network
  1. Deploy NPM with the Macvlan network using Container Managerโ€™s โ€œProjectโ€ feature

Option B: Use Different Ports

Simply map NPM to non-standard ports (e.g., 8080:80, 8443:443).

3.3.2 Container Manager Deployment

  1. Open Container Manager โ†’ Project
  2. Click Create
  3. Set project name: nginx-proxy-manager
  4. Select Use Docker Compose
  5. Paste the appropriate docker-compose.yml (with Macvlan network modifications if using Option A)
  6. Click Next โ†’ Done

3.4 TrueNAS Scale Installation

TrueNAS Scale v22.12+ has built-in Docker support via the Apps section.

Step 1: Change TrueNAS Web UI Ports

Before installing NPM, change TrueNASโ€™s default ports to avoid conflicts:

  1. Go to System Settings โ†’ General โ†’ GUI
  2. Change Web Interface HTTP Port from 80 to 880
  3. Change Web Interface HTTPS Port from 443 to 8443
  4. Click Save

Step 2: Install via Apps

  1. Navigate to Apps โ†’ Available Applications
  2. Search for nginx-proxy-manager
  3. Click Install
  4. Configure ports (80, 443, 81)
  5. Click Save

Step 3: Access NPM

Click the Web Portal button in the Apps section to open the admin interface.

3.5 UnRAID Installation

Step 1: Change UnRAID Web UI Ports

  1. Go to Settings โ†’ Management Access
  2. Change HTTP port from 80 to 81 (or 8080)
  3. Change HTTPS port from 443 to 444 (or 8443)
  4. Click Apply

Step 2: Install via Community Applications

  1. Open Apps tab
  2. Search for nginx proxy manager
  3. Select the NginxProxyManager template by jc21
  4. Configure paths and ports
  5. Click Apply

Step 3: Router Port Forwarding

Forward ports 80 and 443 from your router to the UnRAID server IP.

3.6 Portainer Deployment

If youโ€™re using Portainer for Docker management:

  1. Go to Stacks โ†’ Add Stack
  2. Name: nginx-proxy-manager
  3. Paste the Docker Compose YAML
  4. Click Deploy the stack

4. Basic Configuration

4.1 Dashboard Overview

The NPM dashboard provides access to:

SectionPurpose
DashboardOverview of proxy hosts, certificates, and statistics
Hosts โ†’ Proxy HostsConfigure reverse proxy entries
Hosts โ†’ Redirection HostsHTTP redirections
Hosts โ†’ StreamsTCP/UDP proxying
Hosts โ†’ 404 HostsCustom error pages
SSL CertificatesManage Letโ€™s Encrypt and custom certificates
Access ListsIP restrictions and HTTP authentication
UsersUser management and permissions
Audit LogActivity history
SettingsDefault site behavior

4.2 Creating Your First Proxy Host

Letโ€™s create a proxy host for a service running at 192.168.1.50:8080.

Step 1: Navigate to Proxy Hosts

  • Click Hosts โ†’ Proxy Hosts โ†’ Add Proxy Host

Step 2: Configure Details Tab

FieldValueExplanation
Domain Namesmyapp.example.comYour fully qualified domain
SchemehttpProtocol to backend (http or https)
Forward Hostname/IP192.168.1.50Internal IP of the service
Forward Port8080Port the service listens on
Cache Assetsโ˜‘๏ธImproves performance
Block Common Exploitsโ˜‘๏ธSecurity hardening
Websockets Supportโ˜‘๏ธRequired for WebSocket apps

Step 3: Configure SSL Tab

FieldValue
SSL CertificateRequest a new SSL Certificate
Force SSLโ˜‘๏ธ
HTTP/2 Supportโ˜‘๏ธ
HSTS Enabledโ˜‘๏ธ

Step 4: Save Click Save to create the proxy host.

4.3 Access Lists (IP-Based Access Control)

Restrict access to services based on IP addresses.

Create an Access List:

  1. Go to Access Lists โ†’ Add Access List
  2. Name: internal-only
  3. Under Access:
    • Click Add
    • Enter your local network: 192.168.1.0/24 โ†’ Directive: allow
    • Add another: all โ†’ Directive: deny
  4. Optional: Enable HTTP Basic Authentication
    • Add username/password pairs
  5. Click Save

Apply to Proxy Host:

  1. Edit your proxy host
  2. Go to Details tab
  3. Set Access List to internal-only
  4. Save

4.4 Redirection Hosts

Redirect one domain to another (e.g., redirect www.example.com to example.com).

  1. Go to Hosts โ†’ Redirection Hosts โ†’ Add Redirection Host
  2. Configure:
    • Domain Names: www.example.com
    • Scheme: https
    • Forward Domain Name: example.com
    • Preserve Path: โ˜‘๏ธ
    • HTTP Code: 301 (permanent redirect)
  3. Configure SSL (can use existing wildcard certificate)
  4. Click Save

4.5 404 Hosts

Serve custom error pages for unrecognized domains:

  1. Go to Hosts โ†’ 404 Hosts โ†’ Add 404 Host
  2. Enter domain names that should show the custom 404 page
  3. Configure SSL if needed
  4. Customize the error page via Advanced configuration

5. SSL Certificate Management

5.1 Letโ€™s Encrypt Certificates (HTTP Challenge)

The HTTP challenge is the simplest method but requires:

  • Port 80 accessible from the internet
  • DNS A record pointing to your public IP

Automatic SSL via Proxy Host:

When creating a proxy host:

  1. Go to SSL tab
  2. Select Request a new SSL Certificate
  3. Check โ˜‘๏ธ Force SSL
  4. Check โ˜‘๏ธ I Agree to the Letโ€™s Encrypt Terms of Service
  5. Enter your email for expiration notices
  6. Save

NPM will automatically:

  • Request the certificate from Letโ€™s Encrypt
  • Configure Nginx to use it
  • Renew it before expiration

5.2 Wildcard Certificates (DNS Challenge)

Wildcard certificates (*.example.com) require DNS validation. This is ideal for:

  • Covering all subdomains with one certificate
  • Services not publicly accessible (internal only)
  • Avoiding port 80 exposure

Step 1: Get DNS Provider API Token

Cloudflare Example:

  1. Log in to Cloudflare Dashboard
  2. Go to My Profile โ†’ API Tokens โ†’ Create Token
  3. Use Edit zone DNS template
  4. Configure:
    • Zone Resources: Include โ†’ Specific zone โ†’ Your domain
    • TTL: 0 (no expiration) or set an appropriate TTL
  5. Click Create Token
  6. Copy the token (shown only once)

Step 2: Request Wildcard Certificate in NPM

  1. Go to SSL Certificates โ†’ Add SSL Certificate โ†’ Letโ€™s Encrypt
  2. Configure:
    • Domain Names: *.example.com (and optionally example.com)
    • โ˜‘๏ธ Use a DNS Challenge
    • DNS Provider: Cloudflare
  3. In Credentials File Content, enter:
dns_cloudflare_api_token=YOUR_API_TOKEN_HERE
  1. Set Propagation Seconds: 120 (gives DNS time to update)
  2. Agree to Terms of Service
  3. Click Save

Supported DNS Providers:

  • Cloudflare (recommended)
  • AWS Route53
  • DigitalOcean
  • Gandi
  • Google Cloud DNS
  • Linode
  • OVH
  • And many more (40+ providers)

[!TIP] View all supported DNS providers and their credential formats at the certbot-dns-cloudflare documentation and similar for other providers.

5.3 Custom SSL Certificates

Upload your own certificates (e.g., purchased or from internal CA):

  1. Go to SSL Certificates โ†’ Add SSL Certificate โ†’ Custom
  2. Name: Descriptive name for the certificate
  3. Upload:
    • Certificate: Your .crt or .pem file
    • Certificate Key: Your private .key file
    • Intermediate Certificate: (Optional) CA bundle
  4. Click Save

5.4 Certificate Renewal

  • Letโ€™s Encrypt certificates are automatically renewed 30 days before expiration
  • Check certificate status in SSL Certificates section
  • Force renewal by deleting and re-creating the certificate if issues occur

6. Home Lab Use Cases & Examples

6.1 Proxying Jellyfin (Media Server)

Jellyfin is a popular open-source media server.

Proxy Host Configuration:

FieldValue
Domain Namesjellyfin.example.com
Schemehttp
Forward Hostname/IP192.168.1.50 (Jellyfin server IP)
Forward Port8096
Cache Assetsโ˜‘๏ธ
Block Common Exploitsโ˜‘๏ธ
Websockets Supportโ˜‘๏ธ (required for live TV)

Advanced Tab (for large file uploads):

client_max_body_size 20G;

Jellyfin Configuration (optional - in Jellyfin Dashboard):

  • Networking โ†’ Base URL: Leave empty
  • Networking โ†’ Known Proxies: Add NPMโ€™s IP

6.2 Proxying Nextcloud

Nextcloud requires specific headers and WebSocket support.

Proxy Host Configuration:

FieldValue
Domain Namescloud.example.com
Schemehttp
Forward Hostname/IP192.168.1.60
Forward Port80 (or 8080 for Docker)
Cache AssetsโŒ (let Nextcloud handle caching)
Block Common Exploitsโ˜‘๏ธ
Websockets Supportโ˜‘๏ธ

Advanced Tab:

proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 10G;

Nextcloud config.php: Add NPMโ€™s domain to trusted domains:

'trusted_domains' => 
  array (
    0 => 'localhost',
    1 => 'cloud.example.com',
  ),
'trusted_proxies' => ['192.168.1.100'], // NPM's IP
'overwriteprotocol' => 'https',

6.3 Proxying Plex

Plex has its own remote access, but NPM provides consistent SSL.

Proxy Host Configuration:

FieldValue
Domain Namesplex.example.com
Schemehttp
Forward Hostname/IP192.168.1.70
Forward Port32400
Websockets Supportโ˜‘๏ธ

Advanced Tab:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# WebSocket support for Plex
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

[!NOTE] Plexโ€™s built-in โ€œRemote Accessโ€ may show as unavailable when using a reverse proxy. This is expectedโ€”access Plex via your NPM domain instead.

6.4 Proxying Home Assistant

Home Assistant requires WebSocket support for the frontend.

Proxy Host Configuration:

FieldValue
Domain Nameshass.example.com
Schemehttp
Forward Hostname/IP192.168.1.80
Forward Port8123
Websockets Supportโ˜‘๏ธ (critical!)
Block Common Exploitsโ˜‘๏ธ

Home Assistant configuration.yaml:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 192.168.1.100  # NPM's IP
    - 172.16.0.0/12  # Docker network (if applicable)

6.5 Proxying Grafana

Proxy Host Configuration:

FieldValue
Domain Namesgrafana.example.com
Schemehttp
Forward Hostname/IP192.168.1.90
Forward Port3000
Websockets Supportโ˜‘๏ธ

Grafana environment variables:

GF_SERVER_ROOT_URL=https://grafana.example.com/
GF_SERVER_DOMAIN=grafana.example.com

6.6 Complete Home Lab Example Stack

Hereโ€™s a Docker Compose example with multiple services behind NPM:

services:
  # Nginx Proxy Manager
  npm:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: npm
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
      - '81:81'
    volumes:
      - ./npm/data:/data
      - ./npm/letsencrypt:/etc/letsencrypt
    networks:
      - proxy_network

  # Jellyfin Media Server
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    volumes:
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - /mnt/media:/media:ro
    networks:
      - proxy_network

  # Nextcloud
  nextcloud:
    image: nextcloud:apache
    container_name: nextcloud
    restart: unless-stopped
    volumes:
      - ./nextcloud:/var/www/html
    environment:
      - NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com
    networks:
      - proxy_network

  # Portainer
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./portainer:/data
    networks:
      - proxy_network

networks:
  proxy_network:
    driver: bridge

Tip: Use container names as forward hostnames (e.g., jellyfin instead of IP addresses) when services are on the same Docker network.


7. External Access Configuration

7.1 Cloudflare Tunnels (Zero Trust)

Cloudflare Tunnels provide secure external access without port forwarding.

Advantages:

  • No port forwarding required
  • DDoS protection
  • Hides your public IP
  • Works behind CGNAT

Step 1: Install Cloudflared

Linux:

# Download and install
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloudflare-main.gpg
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared -y

Docker:

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel run
    environment:
      - TUNNEL_TOKEN=YOUR_TUNNEL_TOKEN
    networks:
      - proxy_network

Step 2: Create Tunnel in Cloudflare Dashboard

  1. Go to Cloudflare Zero Trust
  2. Navigate to Access โ†’ Tunnels
  3. Click Create a tunnel
  4. Name: home-lab
  5. Install connector using provided command
  6. Configure public hostnames:
    • Hostname: *.example.com
    • Service: http://npm:80 (or NPMโ€™s Docker container name)

Step 3: Point Tunnel to NPM

Configure the tunnel to send all traffic to NPM, which then routes to internal services.

7.2 Dynamic DNS (DDNS)

If you donโ€™t have a static IP, use DDNS to keep DNS records updated.

Cloudflare DDNS Docker:

services:
  ddns:
    image: oznu/cloudflare-ddns:latest
    container_name: cloudflare-ddns
    restart: unless-stopped
    environment:
      - API_KEY=your_cloudflare_api_token
      - ZONE=example.com
      - SUBDOMAIN=home
      - PROXIED=false

Other DDNS Options:

  • DuckDNS (free)
  • No-IP
  • Dynu
  • Many routers have built-in DDNS support

7.3 Port Forwarding (Traditional Method)

If using traditional port forwarding:

  1. Configure Static IP for your NPM server or use a DHCP reservation
  2. Access Router Settings (usually 192.168.1.1 or 192.168.0.1)
  3. Create Port Forwards:
    • External Port 80 โ†’ NPM IP:80
    • External Port 443 โ†’ NPM IP:443
  4. Create DNS A Record pointing your domain to your public IP

[!WARNING] Only ports 80 and 443 should be forwarded. Never forward port 81 (admin interface).


8. Security Hardening

8.1 Securing the Admin Interface

Method 1: Proxy NPM Through Itself

Create a proxy host for the admin interface:

FieldValue
Domain Namesnpm.example.com
Schemehttp
Forward Hostname/IP127.0.0.1
Forward Port81

Apply:

  • SSL Certificate
  • Access List (internal IPs only)
  • HTTP Basic Authentication

Then modify your Docker Compose to only expose port 81 on localhost:

ports:
  - '80:80'
  - '443:443'
  - '127.0.0.1:81:81'  # Only localhost

8.2 Security Headers

Add these headers in the Advanced tab for enhanced security:

# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

8.3 fail2ban Integration

fail2ban monitors NPM logs and bans malicious IPs.

Step 1: Install fail2ban

sudo apt install fail2ban -y

Step 2: Mount NPM Logs

Update your docker-compose.yml:

volumes:
  - ./data:/data
  - ./letsencrypt:/etc/letsencrypt
  - /var/log/npm:/data/logs  # Add this line

Step 3: Create fail2ban Filter

Create /etc/fail2ban/filter.d/npm.conf:

[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD).*" (4[0-9]{2}|5[0-9]{2})
ignoreregex = 

Step 4: Create fail2ban Jail

Create /etc/fail2ban/jail.d/npm.local:

[npm]
enabled = true
filter = npm
logpath = /var/log/npm/proxy-host-*_access.log
maxretry = 5
findtime = 600
bantime = 3600
action = iptables-allports[name=npm, chain=DOCKER-USER]

Step 5: Restart fail2ban

sudo systemctl restart fail2ban
sudo fail2ban-client status npm

8.4 Cloudflare Integration for Security

If using Cloudflare:

Whitelist Cloudflare IPs:

Add to your jail.local:

ignoreip = 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32

Get Real Client IP:

NPM automatically uses headers from Cloudflare when configured. Ensure your proxy host is receiving CF-Connecting-IP.

8.5 Rate Limiting

Add rate limiting in the Advanced tab:

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
limit_req zone=one burst=5 nodelay;

8.6 CrowdSec (Alternative to fail2ban)

CrowdSec provides community-driven threat intelligence with more advanced detection than fail2ban.

Step 1: Deploy CrowdSec

services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    volumes:
      - ./crowdsec/data:/var/lib/crowdsec/data
      - ./crowdsec/config:/etc/crowdsec
      - /var/log/npm:/var/log/npm:ro  # NPM logs
    environment:
      - COLLECTIONS=crowdsecurity/nginx

Step 2: Install the Bouncer

After CrowdSec is running, install the firewall bouncer:

# Get bouncer API key
docker exec crowdsec cscli bouncers add nginx-bouncer

# Install bouncer on host
sudo apt install crowdsec-firewall-bouncer-iptables

Step 3: Configure NPM Log Parsing

Create /etc/crowdsec/acquis.yaml:

filenames:
  - /var/log/npm/proxy-host-*_access.log
labels:
  type: nginx

[!TIP] CrowdSec offers community-maintained blocklists and can share threat intelligence across installations. Consider it for advanced security needs.


9. SSO Integration

9.1 Authelia Integration

Authelia provides 2FA and SSO for your applications.

Step 1: Deploy Authelia

services:
  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: unless-stopped
    volumes:
      - ./authelia/config:/config
    environment:
      - TZ=America/New_York
    networks:
      - proxy_network

Step 2: Create NPM Snippets

Create /data/nginx/custom/authelia-authrequest.conf:

auth_request /authelia;
auth_request_set $target_url $scheme://$http_host$request_uri;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
auth_request_set $name $upstream_http_remote_name;
auth_request_set $email $upstream_http_remote_email;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Name $name;
proxy_set_header Remote-Email $email;
error_page 401 =302 https://auth.example.com/?rd=$target_url;

Create /data/nginx/custom/authelia-location.conf:

location /authelia {
    internal;
    set $upstream_authelia http://authelia:9091/api/verify;
    proxy_pass $upstream_authelia;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Content-Length "";
    proxy_pass_request_body off;
}

Step 3: Protect Proxy Host

In the Advanced tab of your proxy host:

include /data/nginx/custom/authelia-location.conf;
include /data/nginx/custom/authelia-authrequest.conf;

9.2 Authentik Integration

Authentik is a more feature-rich identity provider with OAuth2/OIDC support.

Step 1: Deploy Authentik

services:
  authentik-server:
    image: ghcr.io/goauthentik/server:latest
    container_name: authentik-server
    restart: unless-stopped
    command: server
    environment:
      AUTHENTIK_SECRET_KEY: YOUR_SECRET_KEY
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: authentik
      AUTHENTIK_POSTGRESQL__NAME: authentik
    volumes:
      - ./authentik/media:/media
      - ./authentik/templates:/templates
    networks:
      - proxy_network

Step 2: Configure Authentik Provider

  1. Create an Application in Authentik for each service
  2. Create a Proxy Provider with Forward Auth settings
  3. Configure the Outpost to handle authentication

Step 3: Configure NPM

Add to Advanced tab of protected proxy host:

auth_request /outpost.goauthentik.io/auth/nginx;
error_page 401 = @goauthentik_proxy_signin;
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;

location /outpost.goauthentik.io {
    proxy_pass http://authentik-outpost:9000/outpost.goauthentik.io;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    add_header Set-Cookie $auth_cookie;
    auth_request_set $auth_cookie $upstream_http_set_cookie;
}

location @goauthentik_proxy_signin {
    internal;
    add_header Set-Cookie $auth_cookie;
    return 302 /outpost.goauthentik.io/start?rd=$request_uri;
}

10. Advanced Features

10.1 Stream Hosts (TCP/UDP Proxying)

Stream hosts proxy non-HTTP traffic, perfect for:

  • Gaming servers (Minecraft, Valheim)
  • Database connections
  • SSH forwarding
  • Custom TCP/UDP services

Example: Minecraft Java Server

  1. Go to Hosts โ†’ Streams โ†’ Add Stream
  2. Configure:
    • Incoming Port: 25565
    • Forward Host: 192.168.1.100 (Minecraft server IP)
    • Forward Port: 25565
    • โ˜‘๏ธ TCP Forwarding
    • โ˜ UDP Forwarding (not needed for Java Edition)
  3. Click Save

Example: Minecraft Bedrock Server

Create two stream hosts:

Stream 1 (UDP):

  • Incoming Port: 19132
  • Forward Port: 19132
  • โ˜‘๏ธ UDP Forwarding

Stream 2 (TCP - optional):

  • Incoming Port: 19133
  • Forward Port: 19133
  • โ˜‘๏ธ TCP Forwarding

Remember: Update your Docker Compose to expose stream ports:

ports:
  - '80:80'
  - '443:443'
  - '81:81'
  - '25565:25565'     # Minecraft Java
  - '19132:19132/udp' # Minecraft Bedrock

10.2 Custom Nginx Configuration

NPM allows custom Nginx directives at various levels.

Custom Configuration Files:

Mount custom config files in these paths:

PathScope
/data/nginx/custom/root_top.confTop of nginx.conf
/data/nginx/custom/root.confEnd of nginx.conf
/data/nginx/custom/http_top.confTop of http block
/data/nginx/custom/http.confEnd of http block
/data/nginx/custom/server_proxy.confAll proxy server blocks

Example: Global Gzip Configuration

Create /data/nginx/custom/http.conf:

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript application/json;
gzip_disable "MSIE [1-6].";

Example: Load Balancing with Upstream

For distributing traffic across multiple backend servers, add this to the Advanced tab of a proxy host:

upstream backend_pool {
    server 192.168.1.101:80 weight=3;
    server 192.168.1.102:80 weight=2;
    server 192.168.1.103:80 backup;
}

location / {
    proxy_pass http://backend_pool;
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}

Load Balancing Methods:

MethodDescription
Round Robin (default)Distributes requests evenly
WeightedUse weight=N for priority
IP HashAdd ip_hash; for session persistence
Least ConnectionsAdd least_conn; to route to least busy

10.3 Using Docker Secrets

For enhanced security, use Docker secrets for passwords:

services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    environment:
      DB_MYSQL_PASSWORD__FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

10.4 Environment Variables Reference

VariableDefaultDescription
TZUTCContainer timezone
PUID-Process user ID
PGID-Process group ID
DISABLE_IPV6falseDisable IPv6 support
DB_SQLITE_FILE/data/database.sqliteSQLite database path
DB_MYSQL_HOST-MySQL/MariaDB hostname
DB_MYSQL_PORT3306MySQL/MariaDB port
DB_MYSQL_USER-MySQL/MariaDB username
DB_MYSQL_PASSWORD-MySQL/MariaDB password
DB_MYSQL_NAME-MySQL/MariaDB database name
DB_POSTGRES_HOST-PostgreSQL hostname
DB_POSTGRES_PORT5432PostgreSQL port
DB_POSTGRES_USER-PostgreSQL username
DB_POSTGRES_PASSWORD-PostgreSQL password
DB_POSTGRES_NAME-PostgreSQL database name

[!TIP] Append __FILE to any environment variable name to load its value from a file (Docker secrets support).


11. Maintenance & Backup

11.1 Updating NPM

Using Docker Compose:

# Navigate to your compose directory
cd ~/npm-docker

# Pull latest image
docker compose pull

# Recreate container with new image
docker compose up -d

# Clean up old images
docker image prune -f

Using Portainer:

  1. Go to your NPM stack
  2. Click Pull and redeploy
  3. Check Re-pull image
  4. Click Update

11.2 Backup Strategy

Essential data to backup:

DirectoryContains
./dataConfiguration, database (SQLite), nginx configs
./letsencryptSSL certificates
./mysql (if using)MariaDB database files

Backup Script (Linux):

#!/bin/bash
# npm-backup.sh

BACKUP_DIR="/backup/npm"
SOURCE_DIR="/path/to/npm-docker"
DATE=$(date +%Y%m%d_%H%M%S)

# Stop container for consistent backup
docker compose -f ${SOURCE_DIR}/docker-compose.yml stop

# Create backup
tar -czvf ${BACKUP_DIR}/npm-backup-${DATE}.tar.gz 
  -C ${SOURCE_DIR} data letsencrypt mysql

# Restart container
docker compose -f ${SOURCE_DIR}/docker-compose.yml start

# Keep only last 7 backups
ls -tp ${BACKUP_DIR}/npm-backup-*.tar.gz | tail -n +8 | xargs -I {} rm -- {}

echo "Backup completed: npm-backup-${DATE}.tar.gz"

Windows (PowerShell):

$BackupDir = "C:Backups\npm"
$SourceDir = "C:DockerData\npm-docker"
$Date = Get-Date -Format "yyyyMMdd_HHmmss"

# Stop container
docker compose -f "$SourceDirdocker-compose.yml" stop

# Create backup
Compress-Archive -Path "$SourceDirdata", "$SourceDirletsencrypt" `
  -DestinationPath "$BackupDir\npm-backup-$Date.zip"

# Start container
docker compose -f "$SourceDirdocker-compose.yml" start

Write-Host "Backup completed: npm-backup-$Date.zip"

11.3 Restore from Backup

Linux:

# Stop current container
docker compose down

# Restore backup
tar -xzvf /backup/npm/npm-backup-YYYYMMDD.tar.gz -C /path/to/npm-docker/

# Start container
docker compose up -d

Windows:

# Stop container
docker compose down

# Restore backup
Expand-Archive -Path "C:Backups\npm\npm-backup-YYYYMMDD.zip" -DestinationPath "C:DockerData\npm-docker" -Force

# Start container
docker compose up -d

11.4 Log Management

NPM stores logs in /data/logs/. Manage log size with:

Custom Log Rotation:

Create /data/nginx/custom/http_top.conf:

log_format main '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent"';
access_log /data/logs/access.log main buffer=16k;

Host-level Log Rotation (Linux):

Create /etc/logrotate.d/npm:

/path/to/npm-docker/data/logs/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    sharedscripts
    postrotate
        docker kill -s USR1 nginx-proxy-manager
    endscript
}

12. Troubleshooting

12.1 Common Issues

Port 80/443 Already in Use

Symptoms: Container fails to start, port binding error

Solution (Linux):

# Find what's using the port
sudo lsof -i :80
sudo lsof -i :443

# Stop the conflicting service
sudo systemctl stop apache2  # or nginx, etc.
sudo systemctl disable apache2

Solution (Windows):

# Find process using port
netstat -ano | findstr :80
netstat -ano | findstr :443

# Kill process (replace PID)
taskkill /PID <PID> /F

SSL Certificate Not Renewing

Symptoms: Certificate expires, HTTPS stops working

Troubleshooting:

  1. Check logs: docker compose logs app | grep -i "letsencrypt\|cert"
  2. Verify port 80 is accessible from internet
  3. Check DNS records point to correct IP
  4. Force renew: Delete certificate in UI, recreate it

502 Bad Gateway

Symptoms: Proxy host returns 502 error

Causes & Solutions:

  1. Backend service not running: Start the backend service
  2. Wrong IP/Port: Verify forward hostname and port
  3. Firewall blocking: Check firewall rules between NPM and backend
  4. Docker network issues: Ensure containers are on same network
  5. Backend expects HTTPS: Change scheme from http to https

WebSocket Connection Failed

Symptoms: Real-time features donโ€™t work (chat, live updates)

Solution:

  1. Enable Websockets Support in proxy host
  2. Add to Advanced tab:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

12.2 Viewing Logs

Container Logs:

# Real-time logs
docker compose logs -f

# Last 100 lines
docker compose logs --tail 100

# Specific service
docker compose logs app

Nginx Error Logs:

# Inside container
docker exec -it nginx-proxy-manager cat /data/logs/fallback_error.log

# From host (if volumes mounted)
cat ./data/logs/fallback_error.log

12.3 Resetting Admin Password

If you forget your admin password:

# Access container shell
docker exec -it nginx-proxy-manager bash

# Reset password via SQLite (if using SQLite)
sqlite3 /data/database.sqlite "UPDATE user SET is_disabled=0 WHERE email='admin@example.com';"

# Or delete user and recreate on restart
sqlite3 /data/database.sqlite "DELETE FROM user WHERE email='admin@example.com';"

Then restart the containerโ€”a new admin user will be created.

12.4 Performance Tuning

For High Traffic:

Add to /data/nginx/custom/http.conf:

worker_connections 4096;
keepalive_timeout 65;
keepalive_requests 10000;

# Proxy buffer optimization
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;

For MariaDB:

Adjust innodb_buffer_pool_size based on available RAM:

  • 4 GB RAM: 256M
  • 8 GB RAM: 512M-1G
  • 16 GB+ RAM: 1G-2G

13. Quick Reference

13.1 Docker Commands Cheatsheet

CommandPurpose
docker compose up -dStart containers in background
docker compose downStop and remove containers
docker compose pullPull latest images
docker compose logs -fFollow container logs
docker compose restartRestart all containers
docker exec -it npm bashAccess container shell
docker stats npmMonitor resource usage

13.2 Important Paths

Path (Container)Purpose
/dataMain configuration directory
/data/database.sqliteSQLite database
/data/nginxNginx configuration files
/data/logsAccess and error logs
/etc/letsencryptSSL certificates

13.3 Default Ports

PortService
80HTTP (public)
443HTTPS (public)
81Admin web interface

14. Conclusion

Nginx Proxy Manager transforms the complex task of managing reverse proxies into a straightforward, visual experience. Whether youโ€™re running a small home lab with a few services or managing dozens of self-hosted applications, NPM provides the tools you need:

Key Takeaways:

  • โœ… Easy SSL: Automatic Letโ€™s Encrypt certificates with one-click setup
  • โœ… Visual Management: No more editing Nginx config files
  • โœ… Home Lab Ready: Perfect for Jellyfin, Nextcloud, Home Assistant, and more
  • โœ… Secure by Default: Built-in security features with optional hardening
  • โœ… Flexible Deployment: Docker, NAS, Raspberry Piโ€”it runs everywhere

Next Steps:

  1. Set up NPM with the basic Docker Compose configuration
  2. Create your first proxy host for an existing service
  3. Implement SSL with Letโ€™s Encrypt
  4. Secure external access using Cloudflare Tunnels or VPN
  5. Add security hardening with fail2ban and access lists

Happy proxying! ๐Ÿš€

Comments

Sign in to join the discussion!

Your comments help others in the community.