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.comhttps://forum.homelab.example.comhttps://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:
- Click Stacks in left sidebar
- Click + Add stack
- Name:
nginx-proxy-manager - 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
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?
- Recommended: Use a password manager (Bitwarden, 1Password, Proton Pass, KeePassXC)
- Generate via command line:
# 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:
- Namecheap (~$15/year)
- Cloudflare (~$10/year)
- Porkbun (~$10/year)
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:
- Configure your router or DNS server (like Pi-hole, OPNsense) to resolve your domain locally
- Create a wildcard DNS entry:
*.homelab.example.com>192.168.1.100 - 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:
- Point domain to your public IP at your registrar
- Configure port forwarding on router:
- Forward port 80 > 192.168.1.100:80
- Forward port 443 > 192.168.1.100:443
- 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
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:
- Go to SSL Certificates
- Click Add SSL Certificate
- Select Let's Encrypt
- Domain Names: Enter your domain (e.g.,
photos.homelab.example.com) - Email: Your email for renewal notifications
- Use a DNS Challenge: Leave unchecked
- Agree to Terms: Check the box
- 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:
- Go to SSL Certificates
- Click Add SSL Certificate
- Select Let's Encrypt
- Domain Names: Enter your domain
- Use a DNS Challenge: Check this box
- DNS Provider: Select your provider
- Credentials: Enter API credentials
- Propagation Seconds: 120 (2 minutes)
- 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:
- Go to Hosts > Proxy Hosts
- 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
portainerinstead of192.168.1.100:9443 - Use
nginx-proxy-managerfor NPM itself
For future services:
- Connect them to
portainer-networkin 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:
- Go to Access Lists
- Click Add Access List
- Name:
LAN Only - Authorization:
- Satisfy Any: Off
- Pass Auth: Off
- Access:
- Allow:
192.168.1.0/24 - Deny:
all
- Allow:
- 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