Running Docker on Windows WSL2/Ubuntu (Part 3) – Nginx Proxy Manager Setup
Learn how to install and configure Nginx Proxy Manager (NPM) on Windows WSL2/Ubuntu with Docker. Replace Synology DSM’s reverse proxy, issue free Let’s Encrypt SSL certificates, and securely publish containers using a unified edge network.
🐳INtroduction: Why Nginx Proxy Manager?
If you’ve used Synology DSM’s built-in reverse proxy, you already know the drill: central management of domains, SSL certificates, and routing to services running inside your network. But DSM is limited, and as we move to WSL2 + Docker Engine, we need something more flexible.
That’s where Nginx Proxy Manager (NPM) comes in.
- 🔒 Built-in Let’s Encrypt SSL support
- 🌍 Clean GUI interface for reverse proxy rules
- 🔄 Easy per-container configuration (no need to manage one giant wildcard cert)
- ⚡ Works seamlessly with Docker, networks, and edge routing
Think of NPM as your new “DSM reverse proxy,” but better.
STEP 1: The Networking Concept
Before diving into the installation, let’s outline how networking will work in this project:
- edge network (external) – a shared Docker network that NPM lives on. Every container that should be publicly accessible will also join this network.
- internal/private networks – containers can still have their own internal networks for DBs, cron jobs, or worker processes. These stay hidden from the outside.
- dual-network containers – for example, Jellyfin will connect to both its internal media network and the edge network. That way, only the HTTP/HTTPS service is visible to NPM.
- ports hidden – instead of exposing ports like
:9000, everything goes through NPM and gets a clean subdomain (https://app.mydomain.com).
This design mimics what DSM used to do for us, but with more flexibility and isolation.
STEP 2: NPM Installation with Docker Compose
Let’s set up NPM. Create a folder just for it:
mkdir -p ~/docker/npm
cd ~/docker/npm
mkdir data letsencryptHere’s the docker-compose.yml:
services:
npm-edge:
image: jc21/nginx-proxy-manager:latest
container_name: npm-edge
restart: unless-stopped
environment:
- TZ=Asia/Seoul # to your location
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
- "81:81" # NPM Admin UI (temporary)
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- edge
networks:
edge:
external: trueNetworking Setup for NPM
Before deploying Nginx Proxy Manager, let’s prepare the network it will use.
Instead of letting Docker Compose auto-generate an isolated network for each project, we’ll create a shared external bridge network called edge.
Run this once:
docker network create edgeWhy not let Compose create networks automatically?
- External + Reusable – Declaring
edgeasexternal: trueindocker-compose.ymlmeans multiple Compose projects can reuse it. NPM, Portainer, Ghost, Jellyfin—each can connect to the same network seamlessly. - Avoid Duplication – Auto-created networks are project-scoped. That makes cross-project communication messy.
- Predictable Service Discovery – With a single
edgenetwork, containers can find each other by container name without hacks or manual linking.
👉 In short: by creating edge once, we ensure a consistent, reliable networking layer for every container that will sit behind Nginx Proxy Manager.
Hit it up:
docker compose up -dSTEP 3: First Login
Open the NPM admin interface in your browser:
http://<your-wsl-ip-or-domain>:81Default credentials:
- Email:
admin@example.com - Password:
changeme
👉 Change the email/password immediately and set your timezone.
👉 You can set timezone either via the TZ variable in docker-compose.yml or inside the NPM UI (Settings → General). Make sure both match for consistency.
STEP 4: First SSL Certificate
NPM can issue free SSL certs using Let’s Encrypt. Let’s create your first certificate:
- Navigate to SSL Certificates → Add SSL Certificate.
- Choose Request a new SSL Certificate (HTTP-01 Challenge).
- Enter your domain:
- If you’re using DuckDNS:
wody.duckdns.org - If you own a domain:
mydomain.com
- If you’re using DuckDNS:
- Enable options:
- Force SSL
- HTTP/2
- (HSTS later – keep it disabled for now)
Hit save, and NPM will automatically fetch and install the certificate.
👉 You can set timezone either via the TZ variable in docker-compose.yml or inside the NPM UI (Settings → General). Make sure both match for consistency.
🔧 Troubleshooting SSL (Common Issues)
Sometimes the first SSL request fails. Don’t worry — here are the most common causes and fixes:
- Port Forwarding Not Set Up
Let’s Encrypt must reach your server on ports 80 (HTTP) and 443 (HTTPS).- Check your router → forward both ports to your WSL IP (e.g.,
192.168.0.121). - Make sure Windows Firewall isn’t blocking them.
- Check your router → forward both ports to your WSL IP (e.g.,
- Public IP Mismatch
If your domain doesn’t point to your public IP, validation will fail.- For DuckDNS: ensure the IP is updated (
duckdns.orgprovides an update URL or cron script). - For personal domains: verify your DNS A-record points to your ISP’s public IP.
- For DuckDNS: ensure the IP is updated (
- ISP Port Blocking
Some ISPs block port 80. If so, Let’s Encrypt HTTP-01 validation won’t work.- Possible workaround: use a VPN tunnel or Cloudflare proxy.
- Or, switch to DNS-01 challenge (NPM supports it) which bypasses port checks.
- Temporary Rate Limits
Let’s Encrypt has rate limits if you retry too often.- Wait a few hours before retrying.
- Test with Staging Environment option in NPM to avoid hitting production limits.
👉 Once you confirm ports, DNS, and DuckDNS updates are all working, SSL requests usually succeed on the next try.
STEP 5: Accessing NPM via Subdomain
Why did we install Nginx Proxy Manager in the first place?
The very first reason was simple: get rid of those ugly port numbers and replace them with clean, SSL-secured subdomains.
So let’s put NPM to its first real test:
- Go to Hosts → Proxy Hosts → Add Proxy Host
- Fill in:
- Domain Names:
npm.wody.duckdns.org(ornpm.mydomain.com) - Forward Hostname/IP:
npm-edge - Forward Port:
81 - Scheme:
http
- Domain Names:
- Under SSL:
- Select your newly created certificate
- Enable Force SSL and HTTP/2
- Leave HSTS disabled for now
- Save.
Now you can log into NPM at:
👉 https://npm.wody.duckdns.org
Once this works:
- Go back to your
docker-compose.yml - Comment out / remove the
81:81port mapping - (Optional) Re-enable HSTS for stricter HTTPS enforcement
🛠️ Adding Useful Custom Headers
NPM allows you to inject Custom Nginx Configuration per host. This is powerful for security and compatibility.
Here are some recommended headers you can add under Advanced → Custom Nginx Configuration:
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; img-src *; media-src *; frame-ancestors 'self'" always;
# Performance
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;🔒 Why these matter:
- X-Frame-Options / X-Content-Type-Options → protect against clickjacking & MIME sniffing
- Referrer-Policy → limits sensitive referrer data leaks
- Content-Security-Policy (CSP) → reduces XSS risks (you can customize for your app)
- Forwarded headers → ensure apps know the real client IP & protocol
👉 These tweaks make your setup not only cleaner but also more secure and production-ready.
STEP 6: Operating NPM Effectively
A few best practices to keep NPM running smoothly:
- 🔐 Access Control – Use Access Lists in NPM to restrict sensitive apps (e.g., Portainer).
- 🔒 HSTS (later) – Once you confirm everything works, enable HSTS for stricter security.
- 🗂 Certificates per subdomain – Unlike DSM where you batch everything, NPM lets you request SSL certs individually. Add them only as needed.
- ⚡ Backups – The
./dataand./letsencryptfolders contain all config and certificates. Keep them backed up.
Final Thoughts
At this point, NPM is acting as the gateway to your home server. Every new container you install later (Jellyfin, Ghost, Portainer, etc.) will:
- Join the edge network
- Be assigned a unique subdomain via NPM
- Receive its own SSL certificate from Let’s Encrypt
This setup feels a lot like Synology DSM’s reverse proxy, but with more control, better isolation, and a modern SSL workflow.
👉 Unlike DSM, you’re no longer locked into a closed ecosystem. You can run any container, use any domain provider, and scale far beyond the limits of NAS hardware.
If you’ve followed along this far, you should now have a solid grasp of the core concept. And more importantly—you now have the foundation to launch, split, secure, and experiment with as many containers as you want.
But we’re not done yet. There are still a few cases and tools that will make your home server experience smoother. In the next part, we’ll explore Portainer, a powerful GUI for managing Docker. With Portainer, you’ll get the convenience of a dashboard while still keeping all the flexibility of your WSL-based Docker setup.
👉 Next up in Part 4: Installing Portainer for a Better Docker GUI Experience