Nginx Proxy Manager (NPM) is going to act as the reverse proxy for the websites in my homelab.
The goal is to eventually have traffic flow like this:
Internet
↓
Cloudflare DNS
↓
Public IP
↓
OPNsense
↓
Nginx Proxy Manager
↓
Correct website/server
This guide covers the initial network preparation, creation of the NPM container, installation of Docker, and deployment of Nginx Proxy Manager.
The configuration is deliberately kept fairly simple at this stage. More advanced firewall restrictions, Cloudflare configuration, SSL certificates and website proxy hosts will be covered later.
Homelab Network Design
The lab is running on Proxmox with OPNsense providing routing, firewalling and VLAN management.
Two VLANs are being used for the web-hosting environment.
VLAN 10 — Management / Infrastructure
Network: 192.168.10.0/24
Gateway: 192.168.10.1
This VLAN is intended for infrastructure and management services, including:
- Nginx Proxy Manager
- WireGuard
- Monitoring
- Other management services
VLAN 20 — Websites
Network: 192.168.20.0/24
Gateway: 192.168.20.1
This VLAN will contain the actual website workloads, including:
- Manual LEMP installation
- CloudPanel
- Coolify
- WordPress sites
Keeping the reverse proxy and website workloads separated gives us a cleaner foundation for firewall rules and segmentation.
Preparing OPNsense
The Proxmox internal bridge is configured as a VLAN-aware bridge.
The OPNsense VM has a virtual network adapter connected to this bridge and is configured as a VLAN trunk.
The trunk currently carries:
VLAN 10
VLAN 20
Inside OPNsense, the VLANs were created on the internal interface.
VLAN 10
Parent: vtnet1
Tag: 10
Description: MGMT
VLAN 20
Parent: vtnet1
Tag: 20
Description: WEB
The VLAN interfaces were then assigned in OPNsense.
MGMT
192.168.10.1/24
WEB
192.168.20.1/24
DHCP was configured for the web VLAN using Dnsmasq:
192.168.20.100 - 192.168.20.199
Static infrastructure addresses are kept outside this DHCP range.
Firewall Considerations
An important lesson during this setup was that firewall rules can sometimes behave differently from what initially appears obvious.
A management rule was created to allow the MGMT network to access the Internet while preventing access to RFC1918 private networks.
The intended logic was:
MGMT → Internet ALLOW
MGMT → Private LANs BLOCK
However, the rule used an inverted RFC1918 alias.
Because:
192.168.10.1
is itself an RFC1918 address, the rule also prevented the NPM container from reaching its own VLAN gateway.
This initially looked like a VLAN or Proxmox networking problem.
After checking the VLAN configuration, Proxmox bridge, virtual interfaces and OPNsense packet capture, the firewall rule was identified as the actual cause.
This was a useful reminder:
When troubleshooting VLAN connectivity, don’t automatically assume the VLAN is broken. Check the firewall rules as well.
Once the rule was corrected, VLAN 10 connectivity worked as expected.
Creating the Nginx Proxy Manager Container
A Debian 13 LXC was created in Proxmox for Nginx Proxy Manager.
The container was configured with:
CT ID: 103
Hostname: npm01
Operating System: Debian 13
Unprivileged: Yes
CPU: 1 vCPU
RAM: 1 GB
Swap: 512 MB
Disk: 8 GB
Nesting: Enabled
The container was connected to VLAN 10.
Its network configuration was:
IP address: 192.168.10.10/24
Gateway: 192.168.10.1
VLAN: 10
The address is outside the DHCP range and is therefore being used as a static infrastructure address.
Installing Docker
Nginx Proxy Manager will run as a Docker container.
First, the Docker repository signing key was downloaded:
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
The key permissions were then corrected:
chmod a+r /etc/apt/keyrings/docker.asc
The official Docker repository was added:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
The package lists were updated:
apt update
Docker and the required components were installed:
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
In this particular installation, the packages were already at their newest versions.
Verifying Docker
Docker was checked with:
systemctl status docker --no-pager
The important result was:
Active: active (running)
Docker was also enabled to start automatically with the system.
Docker Compose was then checked:
docker compose version
The installed version was:
Docker Compose 5.5.1
At this point Docker and Docker Compose were ready.
Creating the NPM Directory
A dedicated directory was created for Nginx Proxy Manager:
mkdir -p /opt/npm
cd /opt/npm
This keeps the NPM configuration and persistent data together rather than scattering files around the system.
Creating the Docker Compose File
The Docker Compose file was created:
nano /opt/npm/docker-compose.yml
The following configuration was used:
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
The important ports are:
80 HTTP
81 NPM administration interface
443 HTTPS
The two volume mappings ensure that NPM’s application data and Let’s Encrypt certificates are stored outside the container filesystem.
This means the container can be recreated without automatically losing the persistent NPM data.
Starting Nginx Proxy Manager
From /opt/npm, NPM was started with:
cd /opt/npm
docker compose up -d
Docker downloaded the Nginx Proxy Manager image and created the Docker network and container.
The result included:
Image jc21/nginx-proxy-manager:latest Pulled
Network npm_default Created
Container nginx-proxy-manager Started
Checking the Container
The running containers were checked with:
docker ps
The NPM container appeared as:
nginx-proxy-manager
and showed:
Up
The published ports were:
0.0.0.0:80-81
0.0.0.0:443
This confirms that the container is listening for connections on the expected ports.
Checking the NPM Logs
Finally, the container logs were checked:
docker logs nginx-proxy-manager --tail 30
The output showed the initial database migrations completing, default settings being created, SSL renewal being initialized and the backend starting successfully.
The important line was:
Backend PID ... listening on port 3000
No startup errors were reported.
Current Status
At this point the basic Nginx Proxy Manager installation is complete.
The current layout is:
Internet
│
▼
OPNsense
│
VLAN 10 / MGMT
│
▼
┌──────────────────┐
│ npm01 │
│ │
│ Nginx Proxy │
│ Manager │
│ │
│ 192.168.10.10 │
└──────────────────┘
│
│
Later:
│
▼
VLAN 20 / WEB
│
┌────────────┼────────────┐
▼ ▼ ▼
LEMP CloudPanel Coolify
Nginx Proxy Manager is running successfully in its Debian 13 LXC using Docker Compose.
The next stage will be to access the NPM administration interface, perform the initial configuration and then begin configuring it as the reverse proxy for the first website.
What We Have Learned
This stage provided a useful practical exercise in several areas:
- Proxmox VLAN-aware bridges
- VLAN trunking
- OPNsense VLAN interfaces
- OPNsense firewall behaviour
- RFC1918 network restrictions
- Debian 13 administration
- Docker installation
- Docker Compose
- Persistent Docker volumes
- Nginx Proxy Manager deployment
- Basic container troubleshooting
One of the most useful lessons was that a connectivity problem doesn’t necessarily mean the VLAN or virtual networking is wrong. In this case, the underlying VLAN configuration was working; the firewall rule was preventing the expected traffic.
That is exactly the sort of problem that makes a homelab useful for learning.