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)
| Version | Release Date | Key Changes |
|---|---|---|
| 2.13.5 | November 2025 | Latest stable, Certbot plugin updates (Glesys), Docker 28 compatibility |
| 2.12.6 | December 2024 | Bug fixes, improved Letโs Encrypt handling |
| 2.11.x | Mid 2024 | PostgreSQL support, UI improvements |
| 2.10.x | Early 2024 | Stream host enhancements |
[!NOTE] This guide is validated against Nginx Proxy Manager v2.13.5. Docker images are available for
amd64,arm64, andarmv7architectures.
1.3 Why Use Nginx Proxy Manager?
| Benefit | Description |
|---|---|
| Visual Configuration | No need to edit Nginx config files manually |
| Time Savings | Set up reverse proxies in minutes, not hours |
| SSL Automation | Free certificates without complex ACME client setup |
| Centralized Management | Single dashboard for all your proxy hosts |
| Home Lab Friendly | Perfect for self-hosted applications |
| Learning Tool | Understand reverse proxies without the complexity |
1.4 NPM vs Alternatives: When to Choose What
| Requirement | Nginx Proxy Manager | Traefik | Caddy |
|---|---|---|---|
| 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 For | Home labs, beginners | Production, GitOps | Simple 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:
- System Requirements โ Hardware and software prerequisites
- Installation Methods โ Docker Compose, Portainer, NAS devices, Raspberry Pi
- Basic Configuration โ Creating proxy hosts, redirections, and 404 hosts
- SSL Certificate Management โ Letโs Encrypt, DNS challenges, wildcards, custom certs
- Home Lab Use Cases โ Jellyfin, Nextcloud, Plex, Home Assistant, and more
- External Access โ Cloudflare Tunnels, DDNS, port forwarding
- Security Hardening โ fail2ban, security headers, access lists
- SSO Integration โ Authelia and Authentik for single sign-on
- Advanced Features โ Stream hosts, custom Nginx configs, API usage
- Maintenance & Troubleshooting โ Backup, updates, common issues
2. System Requirements
2.1 Hardware Requirements
Minimum Requirements (Personal Use)
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 1 core (x86_64/ARM) | 2+ cores |
| RAM | 512 MB | 1 GB |
| Storage | 1 GB | 5 GB (for logs/certs) |
| Network | 10 Mbps | 100+ Mbps |
Requirements by Deployment Scale
| Scale | Users | Services | RAM | CPU |
|---|---|---|---|---|
| Home Lab | 1-5 | 5-15 | 512 MB - 1 GB | 1 core |
| Small Business | 5-20 | 15-50 | 1-2 GB | 2 cores |
| Production | 20+ | 50+ | 2-4 GB | 4+ 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
| Port | Protocol | Purpose | Required |
|---|---|---|---|
| 80 | TCP | HTTP traffic (public) | Yes |
| 443 | TCP | HTTPS traffic (public) | Yes |
| 81 | TCP | NPM Admin Interface | Internal only |
| Custom | TCP/UDP | Stream hosts | Optional |
[!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:
| Field | Default Value |
|---|---|
admin@example.com | |
| Password | changeme |
[!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
- Open your browser and navigate to:
http://YOUR_SERVER_IP:81 - Log in with default credentials:
admin@example.com/changeme - Immediately change your email and password when prompted
3.1.3 Setup with MariaDB (Recommended for Production)
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_PASSWORDandYOUR_ROOT_PASSWORDwith 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, useyobasystems/alpine-mariadb:latestinstead: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.
Enable SSH on your Synology (Control Panel โ Terminal & SNMP)
Gather Network Info:
- Your Synology IP (e.g.,
192.168.1.100) - Subnet mask (e.g.,
255.255.255.0=/24) - Network interface name:
eth0orovs_eth0
- Your Synology IP (e.g.,
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 - 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
- Open Container Manager โ Project
- Click Create
- Set project name:
nginx-proxy-manager - Select Use Docker Compose
- Paste the appropriate
docker-compose.yml(with Macvlan network modifications if using Option A) - 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:
- Go to System Settings โ General โ GUI
- Change Web Interface HTTP Port from 80 to 880
- Change Web Interface HTTPS Port from 443 to 8443
- Click Save
Step 2: Install via Apps
- Navigate to Apps โ Available Applications
- Search for
nginx-proxy-manager - Click Install
- Configure ports (80, 443, 81)
- 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
- Go to Settings โ Management Access
- Change HTTP port from 80 to 81 (or 8080)
- Change HTTPS port from 443 to 444 (or 8443)
- Click Apply
Step 2: Install via Community Applications
- Open Apps tab
- Search for
nginx proxy manager - Select the
NginxProxyManagertemplate by jc21 - Configure paths and ports
- 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:
- Go to Stacks โ Add Stack
- Name:
nginx-proxy-manager - Paste the Docker Compose YAML
- Click Deploy the stack
4. Basic Configuration
4.1 Dashboard Overview
The NPM dashboard provides access to:
| Section | Purpose |
|---|---|
| Dashboard | Overview of proxy hosts, certificates, and statistics |
| Hosts โ Proxy Hosts | Configure reverse proxy entries |
| Hosts โ Redirection Hosts | HTTP redirections |
| Hosts โ Streams | TCP/UDP proxying |
| Hosts โ 404 Hosts | Custom error pages |
| SSL Certificates | Manage Letโs Encrypt and custom certificates |
| Access Lists | IP restrictions and HTTP authentication |
| Users | User management and permissions |
| Audit Log | Activity history |
| Settings | Default 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
| Field | Value | Explanation |
|---|---|---|
| Domain Names | myapp.example.com | Your fully qualified domain |
| Scheme | http | Protocol to backend (http or https) |
| Forward Hostname/IP | 192.168.1.50 | Internal IP of the service |
| Forward Port | 8080 | Port the service listens on |
| Cache Assets | โ๏ธ | Improves performance |
| Block Common Exploits | โ๏ธ | Security hardening |
| Websockets Support | โ๏ธ | Required for WebSocket apps |
Step 3: Configure SSL Tab
| Field | Value |
|---|---|
| SSL Certificate | Request 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:
- Go to Access Lists โ Add Access List
- Name:
internal-only - Under Access:
- Click Add
- Enter your local network:
192.168.1.0/24โ Directive:allow - Add another:
allโ Directive:deny
- Optional: Enable HTTP Basic Authentication
- Add username/password pairs
- Click Save
Apply to Proxy Host:
- Edit your proxy host
- Go to Details tab
- Set Access List to
internal-only - Save
4.4 Redirection Hosts
Redirect one domain to another (e.g., redirect www.example.com to example.com).
- Go to Hosts โ Redirection Hosts โ Add Redirection Host
- Configure:
- Domain Names:
www.example.com - Scheme:
https - Forward Domain Name:
example.com - Preserve Path: โ๏ธ
- HTTP Code:
301(permanent redirect)
- Domain Names:
- Configure SSL (can use existing wildcard certificate)
- Click Save
4.5 404 Hosts
Serve custom error pages for unrecognized domains:
- Go to Hosts โ 404 Hosts โ Add 404 Host
- Enter domain names that should show the custom 404 page
- Configure SSL if needed
- 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:
- Go to SSL tab
- Select Request a new SSL Certificate
- Check โ๏ธ Force SSL
- Check โ๏ธ I Agree to the Letโs Encrypt Terms of Service
- Enter your email for expiration notices
- 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:
- Log in to Cloudflare Dashboard
- Go to My Profile โ API Tokens โ Create Token
- Use Edit zone DNS template
- Configure:
- Zone Resources: Include โ Specific zone โ Your domain
- TTL: 0 (no expiration) or set an appropriate TTL
- Click Create Token
- Copy the token (shown only once)
Step 2: Request Wildcard Certificate in NPM
- Go to SSL Certificates โ Add SSL Certificate โ Letโs Encrypt
- Configure:
- Domain Names:
*.example.com(and optionallyexample.com) - โ๏ธ Use a DNS Challenge
- DNS Provider: Cloudflare
- Domain Names:
- In Credentials File Content, enter:
dns_cloudflare_api_token=YOUR_API_TOKEN_HERE - Set Propagation Seconds:
120(gives DNS time to update) - Agree to Terms of Service
- 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):
- Go to SSL Certificates โ Add SSL Certificate โ Custom
- Name: Descriptive name for the certificate
- Upload:
- Certificate: Your
.crtor.pemfile - Certificate Key: Your private
.keyfile - Intermediate Certificate: (Optional) CA bundle
- Certificate: Your
- 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:
| Field | Value |
|---|---|
| Domain Names | jellyfin.example.com |
| Scheme | http |
| Forward Hostname/IP | 192.168.1.50 (Jellyfin server IP) |
| Forward Port | 8096 |
| 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:
| Field | Value |
|---|---|
| Domain Names | cloud.example.com |
| Scheme | http |
| Forward Hostname/IP | 192.168.1.60 |
| Forward Port | 80 (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:
| Field | Value |
|---|---|
| Domain Names | plex.example.com |
| Scheme | http |
| Forward Hostname/IP | 192.168.1.70 |
| Forward Port | 32400 |
| 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:
| Field | Value |
|---|---|
| Domain Names | hass.example.com |
| Scheme | http |
| Forward Hostname/IP | 192.168.1.80 |
| Forward Port | 8123 |
| 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:
| Field | Value |
|---|---|
| Domain Names | grafana.example.com |
| Scheme | http |
| Forward Hostname/IP | 192.168.1.90 |
| Forward Port | 3000 |
| 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
- Go to Cloudflare Zero Trust
- Navigate to Access โ Tunnels
- Click Create a tunnel
- Name:
home-lab - Install connector using provided command
- Configure public hostnames:
- Hostname:
*.example.com - Service:
http://npm:80(or NPMโs Docker container name)
- Hostname:
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:
- Configure Static IP for your NPM server or use a DHCP reservation
- Access Router Settings (usually
192.168.1.1or192.168.0.1) - Create Port Forwards:
- External Port 80 โ NPM IP:80
- External Port 443 โ NPM IP:443
- 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:
| Field | Value |
|---|---|
| Domain Names | npm.example.com |
| Scheme | http |
| Forward Hostname/IP | 127.0.0.1 |
| Forward Port | 81 |
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
- Create an Application in Authentik for each service
- Create a Proxy Provider with Forward Auth settings
- 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
- Go to Hosts โ Streams โ Add Stream
- 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)
- Incoming Port:
- 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:
| Path | Scope |
|---|---|
/data/nginx/custom/root_top.conf | Top of nginx.conf |
/data/nginx/custom/root.conf | End of nginx.conf |
/data/nginx/custom/http_top.conf | Top of http block |
/data/nginx/custom/http.conf | End of http block |
/data/nginx/custom/server_proxy.conf | All 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:
| Method | Description |
|---|---|
| Round Robin (default) | Distributes requests evenly |
| Weighted | Use weight=N for priority |
| IP Hash | Add ip_hash; for session persistence |
| Least Connections | Add 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
| Variable | Default | Description |
|---|---|---|
TZ | UTC | Container timezone |
PUID | - | Process user ID |
PGID | - | Process group ID |
DISABLE_IPV6 | false | Disable IPv6 support |
DB_SQLITE_FILE | /data/database.sqlite | SQLite database path |
DB_MYSQL_HOST | - | MySQL/MariaDB hostname |
DB_MYSQL_PORT | 3306 | MySQL/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_PORT | 5432 | PostgreSQL port |
DB_POSTGRES_USER | - | PostgreSQL username |
DB_POSTGRES_PASSWORD | - | PostgreSQL password |
DB_POSTGRES_NAME | - | PostgreSQL database name |
[!TIP] Append
__FILEto 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:
- Go to your NPM stack
- Click Pull and redeploy
- Check Re-pull image
- Click Update
11.2 Backup Strategy
Essential data to backup:
| Directory | Contains |
|---|---|
./data | Configuration, database (SQLite), nginx configs |
./letsencrypt | SSL 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:
- Check logs:
docker compose logs app | grep -i "letsencrypt\|cert" - Verify port 80 is accessible from internet
- Check DNS records point to correct IP
- Force renew: Delete certificate in UI, recreate it
502 Bad Gateway
Symptoms: Proxy host returns 502 error
Causes & Solutions:
- Backend service not running: Start the backend service
- Wrong IP/Port: Verify forward hostname and port
- Firewall blocking: Check firewall rules between NPM and backend
- Docker network issues: Ensure containers are on same network
- Backend expects HTTPS: Change scheme from
httptohttps
WebSocket Connection Failed
Symptoms: Real-time features donโt work (chat, live updates)
Solution:
- Enable Websockets Support in proxy host
- 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
| Command | Purpose |
|---|---|
docker compose up -d | Start containers in background |
docker compose down | Stop and remove containers |
docker compose pull | Pull latest images |
docker compose logs -f | Follow container logs |
docker compose restart | Restart all containers |
docker exec -it npm bash | Access container shell |
docker stats npm | Monitor resource usage |
13.2 Important Paths
| Path (Container) | Purpose |
|---|---|
/data | Main configuration directory |
/data/database.sqlite | SQLite database |
/data/nginx | Nginx configuration files |
/data/logs | Access and error logs |
/etc/letsencrypt | SSL certificates |
13.3 Default Ports
| Port | Service |
|---|---|
| 80 | HTTP (public) |
| 443 | HTTPS (public) |
| 81 | Admin web interface |
13.4 Useful Links
- Official Documentation: nginxproxymanager.com
- GitHub Repository: github.com/NginxProxyManager/nginx-proxy-manager
- Docker Hub: hub.docker.com/r/jc21/nginx-proxy-manager
- Community Forums: Reddit r/selfhosted, r/homelab
- Letโs Encrypt: letsencrypt.org
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:
- Set up NPM with the basic Docker Compose configuration
- Create your first proxy host for an existing service
- Implement SSL with Letโs Encrypt
- Secure external access using Cloudflare Tunnels or VPN
- Add security hardening with fail2ban and access lists
Happy proxying! ๐
Comments
Sign in to join the discussion!
Your comments help others in the community.