Knowledge Base > IT & Systems > Nginx Proxy Manager

Nginx Proxy Manager - Clean URLs & SSL Certificates [Part 5 of 10]

Setting up reverse proxy with automatic HTTPS for all your services


You've got Docker and Portainer running. Now you can deploy services. But there's a problem:

Every service runs on a different port. Want to access your photo app? http://192.168.1.100:2283. Your forum? http://192.168.1.100:8080. Your wiki? http://192.168.1.100:3000.

That's ugly and hard to remember.

What if instead you could use:

  • https://photos.homelab.example.com
  • https://forum.homelab.example.com
  • https://wiki.homelab.example.com

Clean URLs. HTTPS encryption. One tool: Nginx Proxy Manager.


What is a Reverse Proxy?

A reverse proxy sits in front of your services and routes traffic to the right place.

Think of it like a receptionist:

  • You ask for "photos" > They direct you to port 2283
  • You ask for "forum" > They direct you to port 8080
  • You ask for "wiki" > They direct you to port 3000

Benefits:

  • Clean domain names instead of IP:port combinations
  • Automatic SSL certificates (HTTPS)
  • Single entry point for all services
  • Access control and authentication
  • Load balancing (for advanced setups)

Why Nginx Proxy Manager?

Nginx is the industry-standard reverse proxy. It's powerful but complex to configure.

Nginx Proxy Manager (NPM) gives you a beautiful web UI to manage Nginx without editing config files.

Features

  • Web-based GUI - No command-line config editing
  • Free SSL certificates - Automatic Let's Encrypt integration
  • Auto-renewal - SSL certs renew automatically
  • Access lists - Control who can access what
  • Custom locations - Advanced routing rules
  • Stream forwarding - TCP/UDP proxying
  • Dead simple - Seriously, it's that easy

Perfect For

  • HomeLab services with clean URLs
  • Automatic HTTPS for everything
  • Centralizing access to multiple services
  • Learning reverse proxy concepts

What You'll Need

Prerequisites

  • Docker installed (Part 4)
  • Portainer running (Part 4)
  • Domain name (optional but recommended)
    • Paid: Namecheap, Cloudflare, etc. (~$15/year)
    • Free: DuckDNS

Ports Required

  • Port 80 - HTTP traffic
  • Port 443 - HTTPS traffic
  • Port 81 - NPM admin interface

Installing Nginx Proxy Manager

We'll deploy NPM using Portainer Stacks.

Step 1: Create Data Directory

# Create directory for NPM data
sudo mkdir -p /mnt/storage/docker/nginx-proxy-manager/data
sudo mkdir -p /mnt/storage/docker/nginx-proxy-manager/letsencrypt

# Set ownership (replace 'admin' with your username)
sudo chown -R admin:admin /mnt/storage/docker/nginx-proxy-manager

Step 2: Deploy via Portainer

Login to Portainer:

https://192.168.1.100:9443

Create new stack:

  1. Click Stacks in left sidebar
  2. Click + Add stack
  3. Name: nginx-proxy-manager
  4. Build method: Web editor

Paste this compose configuration:

services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - '80:80'    # Public HTTP Port
      - '443:443'  # Public HTTPS Port
      - '81:81'    # Admin Web Port
    environment:
      TZ: "America/New_York"  # Find yours: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
      DISABLE_IPV6: 'true'
    volumes:
      - /mnt/storage/docker/nginx-proxy-manager/data:/data
      - /mnt/storage/docker/nginx-proxy-manager/letsencrypt:/etc/letsencrypt

Note about timezone (TZ):

  • Find your timezone code at: List of TZ Database Timezones
  • Examples: America/New_York, America/Los_Angeles, Europe/London, Asia/Tokyo
  • Use the value from the "TZ identifier" column

Deploy:

  • Scroll down
  • Click Deploy the stack
  • Wait 1-2 minutes for deployment

Step 3: Configure Firewall

On your Ubuntu server (via SSH), allow NPM ports from your local network:

# Allow HTTP from LAN
sudo ufw allow from 192.168.1.0/24 to any port 80 proto tcp comment 'HTTP from LAN'

# Allow HTTPS from LAN
sudo ufw allow from 192.168.1.0/24 to any port 443 proto tcp comment 'HTTPS from LAN'

# Allow NPM admin from LAN
sudo ufw allow from 192.168.1.0/24 to any port 81 proto tcp comment 'NPM Admin from LAN'

# Check firewall status
sudo ufw status numbered

Note: Replace 192.168.1.0/24 with your network range.


Step 4: Access NPM Admin Interface

Open your browser:

http://192.168.1.100:81

Default login credentials:

  • Email: admin@example.com
  • Password: changeme
Important

You'll be forced to change these on first login. Choose a strong password!


Step 5: Initial Setup

On first login:

1. Change email and password

  • Enter your email address
  • Choose a strong password (12+ characters)
  • Click Save

Need a strong password?

# Generate a 20-character random password
openssl rand -base64 20

2. You're in! You should see the NPM dashboard.


Setting Up Your Domain

You have two options for domain names:

Option 1: Paid Domain (Recommended)

Register a domain:

Example: homelab.example.com

For this guide, we'll use: homelab.example.com as a placeholder. Replace with your actual domain.


Option 2: Free DuckDNS

Sign up at DuckDNS

  • Login with Google/GitHub
  • Create a subdomain: yourhomelab.duckdns.org
  • Point it to your home IP address
  • Free forever!

DNS Configuration

You need to point your domain to your server. You have two approaches:

Approach 1: Local DNS Only (What I Use)

Best for: Services only accessed from home network

Setup:

  1. Configure your router or DNS server (like Pi-hole, OPNsense) to resolve your domain locally
  2. Create a wildcard DNS entry: *.homelab.example.com > 192.168.1.100
  3. Domain doesn't resolve publicly (more secure)

Pros:

  • More secure (not exposed to internet)
  • Works without port forwarding
  • No dynamic DNS needed

Cons:

  • Only works on your local network
  • Can't access services remotely

Approach 2: Public DNS + Port Forwarding

Best for: Accessing services from anywhere

Setup:

  1. Point domain to your public IP at your registrar
  2. Configure port forwarding on router:
    • Forward port 80 > 192.168.1.100:80
    • Forward port 443 > 192.168.1.100:443
  3. Use dynamic DNS if you don't have static IP

Pros:

  • Access services from anywhere
  • Works outside your network

Cons:

  • Exposes services to internet (security risk)
  • Requires port forwarding
  • Need dynamic DNS for changing IPs
Important Considerations for Public Access

ISP Terms of Service: Some ISPs prohibit running servers on residential connections. Check your ISP's acceptable use policy.

ISP Equipment: If you're renting a modem/router from your ISP, it may have limited or disabled port forwarding capabilities. Consider using your own router.

Port Blocking: Some ISPs block common ports (80, 443, 25) on residential connections. You may need to use alternative ports.

Dynamic IP: Most residential connections have dynamic IPs that change periodically. Use a dynamic DNS service (DuckDNS, No-IP, etc.) to keep your domain updated.

Security: Only expose services to the internet if you understand the security implications. Use strong passwords, keep services updated, and consider VPN access instead.


Getting SSL Certificates

NPM makes SSL certificates incredibly easy with Let's Encrypt.

Method 1: HTTP Challenge (Easiest)

Requirements:

  • Domain must be publicly accessible
  • Ports 80 and 443 forwarded to your server

Steps in NPM:

  1. Go to SSL Certificates
  2. Click Add SSL Certificate
  3. Select Let's Encrypt
  4. Domain Names: Enter your domain (e.g., photos.homelab.example.com)
  5. Email: Your email for renewal notifications
  6. Use a DNS Challenge: Leave unchecked
  7. Agree to Terms: Check the box
  8. Click Save

NPM will:

  • Request certificate from Let's Encrypt
  • Verify domain ownership via HTTP
  • Install certificate automatically
  • Auto-renew before expiration

Method 2: DNS Challenge (Advanced)

Requirements:

  • API access to your DNS provider
  • Works even without public access

Supported providers:

  • Cloudflare
  • Namecheap
  • DuckDNS
  • Many others

Steps in NPM:

  1. Go to SSL Certificates
  2. Click Add SSL Certificate
  3. Select Let's Encrypt
  4. Domain Names: Enter your domain
  5. Use a DNS Challenge: Check this box
  6. DNS Provider: Select your provider
  7. Credentials: Enter API credentials
  8. Propagation Seconds: 120 (2 minutes)
  9. Click Save

This is what I use - works with local-only DNS.


Creating Your First Proxy Host

Let's proxy Portainer so you can access it via a clean URL.

Step 1: Add Proxy Host

In NPM:

  1. Go to Hosts > Proxy Hosts
  2. Click Add Proxy Host

Step 2: Configure Details Tab

Domain Names:

  • Enter: portainer.homelab.example.com

Scheme:

  • Select: https (Portainer uses HTTPS)

Forward Hostname / IP:

  • Enter: 192.168.1.100 (your server IP)
  • Or: portainer (if using Docker network)

Forward Port:

  • Enter: 9443 (Portainer's port)

Options:

  • Cache Assets - Enable
  • Block Common Exploits - Enable
  • Websockets Support - Enable (important for Portainer)

Step 3: Configure SSL Tab

SSL Certificate:

  • Select your Let's Encrypt certificate
  • Or click Request a new SSL Certificate

Options:

  • Force SSL - Enable
  • HTTP/2 Support - Enable
  • HSTS Enabled - Leave unchecked for now

Step 4: Save and Test

Click: Save

Test it:

https://portainer.homelab.example.com

You should see Portainer! No more remembering ports.


Adding More Services

Repeat the process for each service:

Example: NPM itself

  • Domain: npm.homelab.example.com
  • Forward to: 192.168.1.100:81
  • SSL: Use your certificate

Example: Future services

  • Photos: photos.homelab.example.com > port 2283
  • Forum: forum.homelab.example.com > port 8080
  • Wiki: wiki.homelab.example.com > port 3000

Security Best Practices

Docker Network Setup (Important!)

For NPM to proxy to other Docker containers using container names, they must be on the same network.

Connect NPM to Portainer's network:

On your Ubuntu server (via SSH):

# Connect NPM to portainer-network
docker network connect portainer-network nginx-proxy-manager

# Verify connection
docker inspect nginx-proxy-manager --format='{{range $net,$v := .NetworkSettings.Networks}}{{$net}} {{end}}'

# Should show: nginx-proxy-manager_default portainer-network

Now you can proxy to containers by name:

  • Use portainer instead of 192.168.1.100:9443
  • Use nginx-proxy-manager for NPM itself

For future services:

  • Connect them to portainer-network in their docker-compose files
  • Then use container names (e.g., immich_server, wikijs, discourse)
  • This is cleaner than using IP addresses and works even if IPs change

Access Lists

Restrict access to sensitive services:

  1. Go to Access Lists
  2. Click Add Access List
  3. Name: LAN Only
  4. Authorization:
    • Satisfy Any: Off
    • Pass Auth: Off
  5. Access:
    • Allow: 192.168.1.0/24
    • Deny: all
  6. Click Save

Apply to proxy host:

  • Edit proxy host
  • Access List: Select LAN Only
  • Save

Note: Access lists work best when using IP addresses in the "Forward Hostname/IP" field. If using container names, the access list restricts who can access the domain, not the backend container.


Strong Passwords

  • Use unique, strong passwords for NPM admin
  • Enable 2FA if available (future feature)
  • Don't use default credentials

Keep Updated

  • Regularly update NPM container
  • Check for security advisories
  • Monitor access logs

TL;DR

  • Nginx Proxy Manager provides reverse proxy with web UI
  • Clean URLs instead of IP:port combinations
  • Free SSL certificates via Let's Encrypt
  • Installation: Deploy via Portainer stack
  • Configuration: Add proxy hosts for each service
  • DNS: Local DNS or public with port forwarding
  • Next: We'll install Immich for photo management in Part 6