Nginx Proxy Manager Install Guide for Proxmox VE 9 LXC
Run NPM in a pure Proxmox LXC - no Docker!
The NPM Installation Challenge
Nginx Proxy Manager (NPM) is primarily designed to run in Docker containers. The developers provide:
Docker images - Their primary distribution method
Docker Compose files - For easy Docker deployment
Source code - On GitHub, but no traditional installation packages
There are no .deb packages, no apt repository, and no official install script for bare-metal or LXC installations.
What We’re Actually Doing
Since NPM doesn’t offer a traditional installation method, this guide essentially:
Downloads the NPM source code from GitHub
Extracts the Docker container filesystem (found in
docker/rootfs/)Adapts Docker-specific configurations to work in a standard Linux environment
Manually builds the frontend (the web UI you interact with)
Sets up services that would normally be handled by Docker
Why Not Just Use Docker?
While Docker in LXC is possible, this native approach:
Reduces overhead (no Docker daemon)
Integrates better with LXC
Provides more direct control
Avoids nested containerization complexity
Makes troubleshooting easier
Pure LXC is more elegant
But honestly, it is a lot of work. I wanted to replicate the Helper Script and this is basically how it is done. I might try out Docker inside an LXC although I never like doing that.
Prerequisites
Proxmox VE 9.x installed and running
SSH or console access to Proxmox host
Network connectivity for downloading packages
An available static IP address on your network (e.g., 192.168.10.101)
Your network gateway IP address (e.g., 192.168.10.1)
System Requirements
CPU: 2 cores (for handling multiple proxy connections)
RAM: 1024MB minimum (Node.js requires memory)
Disk: 4GB minimum
Container Type: Unprivileged (more secure)
OS: Debian 12
Step 1: Create the LXC Container
All Step 1 commands should be run inside the Proxmox host
If you just finished up the Pi-hole guide then you will already have Debian 12 downloaded and can skip to the container creation.
Update the template cache to get the latest container templates
pveam updateList available Debian templates
pveam available | grep debian | grep -v turnkeyDownload Debian 12 template to local storage
pveam download local debian-12-standard_12.12-1_amd64.tar.zstCreate the container
pct create 101 local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst \
--hostname nginxproxymanager \
--memory 1024 \
--cores 2 \
--rootfs local-lvm:4 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.10.101/24,gw=192.168.10.1 \
--unprivileged 1 \
--features nesting=1 \
--onboot 1 \
--start 1Complete parameter explanations:
101: Container ID (must be unique, check existing withpct list)local:vztmpl/debian-12...: Template location and name--hostname nginxproxymanager: Container hostname for network identification--memory 1024: Allocate 1GB RAM (1024MB)--cores 2: Allocate 2 CPU cores for better performance--rootfs local-lvm:4: 4GB root filesystem on local-lvm storage--net0 name=eth0,bridge=vmbr0,ip=192.168.10.101/24,gw=192.168.10.1: Network configurationname=eth0: Interface name inside containerbridge=vmbr0: Bridge to use (check withip a | grep vmbr)ip=192.168.10.101/24: Static IP with /24 subnet maskgw=192.168.10.1: Gateway (router) IP address
--unprivileged 1: Run as unprivileged for better security--features nesting=1: Enable nesting for container features--onboot 1: Start automatically when Proxmox boots--start 1: Start container immediately after creation
Verify the container was created and is running
pct status 101Step 2: Enter Container and Initial Setup
Enter the container console
pct enter 101Update package lists and upgrade existing packages
apt update && apt upgrade -yStep 3: Install Base Dependencies
Install essential packages required by NPM
apt install -y \
ca-certificates \
apache2-utils \
logrotate \
build-essential \
git \
curl \
wget \
sudo \
gnupg \
lsb-releaseStep 4: Install Python Dependencies
apt install -y \
python3 \
python3-dev \
python3-pip \
python3-venv \
python3-cffi \
python3-certbot \
python3-certbot-dns-cloudflareInstall additional certbot plugins
pip3 install --break-system-packages certbot-dns-multiCreate Python virtual environment for certbot
python3 -m venv /opt/certbot/Step 5: Install OpenResty (Nginx Fork)
Add OpenResty repository GPG key securely
wget -O - https://openresty.org/package/pubkey.gpg | \
gpg --dearmor -o /usr/share/keyrings/openresty.gpgAdd OpenResty repository with signed-by option
echo “deb [signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package/debian $(lsb_release -sc) openresty” | \
tee /etc/apt/sources.list.d/openresty.listUpdate package lists and install OpenResty
apt update
apt install -y openrestyStep 6: Install Node.js via NVM
Why NVM? Node Version Manager lets us install specific Node.js versions without relying on Debian’s older packages. NPM requires a modern Node.js version.
Version Choice: We’ll install Node.js 22.x (not the absolute latest) because:
It’s stable and well-tested
NPM is tested against LTS versions
LTS receives security updates for 30 months
Newer versions might introduce breaking changes
apt install -y build-essential libssl-devNavigate to temporary directory
cd /tmpIdentify the latest release here https://github.com/nvm-sh/nvm/releases
Download the release
wget https://github.com/nvm-sh/nvm/archive/refs/tags/v0.40.3.tar.gzExtract the tarball
tar -xzf v0.40.3.tar.gzMove to install directory
cd nvm-0.40.3Optional - View Script to Verify Validity
less install.shAfter reviewing and confirming the code is safe, run the installer
./install.shThe installer will:
Create
~/.nvmdirectoryAdd NVM loading script to
~/.bashrcCopy NVM files to the installation directory
Source the NVM script
export NVM_DIR=”$HOME/.nvm”
[ -s “$NVM_DIR/nvm.sh” ] && \. “$NVM_DIR/nvm.sh”
[ -s “$NVM_DIR/bash_completion” ] && \. “$NVM_DIR/bash_completion”Verify NVM installation
nvm --versionCheck Node v22 versions
nvm list-remote | grep “v22” | grep “Latest”Install NodeJS
nvm install 22.21.1Set up default Node
nvm use 22.21.1
nvm alias default 22.21.1Verify Node Installation
node --versionNVM installs Node.js in user directory, but systemd services run as system processes and need Node.js in system paths. This will make it available system-wide
n=$(which node)
n=${n%/bin/node}
chmod -R 755 $n/bin/*
cp -r $n/{bin,lib,share} /usr/local/Verify
/usr/local/bin/node --versionCleanup
cd /tmp
rm -rf nvm-0.40.3
rm v0.40.3.tar.gzStep 7: Install pnpm Package Manager
npm install -g pnpm@8.15Verify
pnpm --versionStep 8: Download Nginx Proxy Manager Source
Locate the latest release https://github.com/NginxProxyManager/nginx-proxy-manager/releases. Make sure to use the one with the “Latest” tag.
Move to tmp folder
cd /tmpThen download the latest tar file
wget https://github.com/NginxProxyManager/nginx-proxy-manager/archive/refs/tags/v2.12.6.tar.gzExtract the tar file
tar -xzf v2.12.6.tar.gzEnter the folder
cd nginx-proxy-manager-2.12.6Step 9: Set Up System Environment
NPM’s source includes Docker-specific configurations that assume Docker’s filesystem layout. We need to adapt these for our standard Linux environment.
Create the required symlink (copy and paste the whole block at once)
ln -sf /usr/bin/python3 /usr/bin/python
ln -sf /usr/bin/certbot /opt/certbot/bin/certbot
ln -sf /usr/local/openresty/nginx/sbin/nginx /usr/sbin/nginx
ln -sf /usr/local/openresty/nginx/ /etc/nginxThe nginx.conf file is configured for Docker, which handles ‘daemon’ mode differently. In a normal system, we need nginx to run as a service, not a daemon.
sed -i ‘s+^daemon+#daemon+g’ docker/rootfs/etc/nginx/nginx.confThis command:
sed -i- Edit file in-places+^daemon+#daemon+g- Find lines starting with “daemon” and comment them outWe use
+as delimiter instead of/because paths contain/
NPM’s nginx config files have includes like include conf.d/something.conf, but in our setup these need to be include /etc/nginx/conf.d/something.conf (full paths).
find “$(pwd)” -type f -name “*.conf” -exec \
sed -i ‘s+include conf.d+include /etc/nginx/conf.d+g’ {} \;Breaking down this complex command:
find “$(pwd)”- Start searching from current directory (the NPM source)-type f- Only find files (not directories)-name “*.conf”- Only files ending in .conf-exec ... {} \;- For each found file, execute the following commandsed -i ‘s+include conf.d+include /etc/nginx/conf.d+g’ {}- Replace the include paths{}- Placeholder for each found file\;- End of the exec command
What it does: Goes through every nginx config file and changes relative includes to absolute paths, so nginx can find the included configurations.
Step 10: Create Directory Structure
NPM expects a specific directory structure to store:
Proxy configurations
SSL certificates
Temporary files
Logs
Cache data
Without these directories, NPM will fail to start or work properly. Copy and paste the whole command.
# Web root and nginx config directories
mkdir -p /var/www/html /etc/nginx/logs
# Nginx needs these for temporary file handling during uploads/proxying
mkdir -p /tmp/nginx/body /run/nginx
# NPM’s data directories - where your configuration is stored
mkdir -p /data/nginx /data/custom_ssl /data/logs /data/access
# Different types of hosts you can configure in NPM
mkdir -p /data/nginx/default_host # Default site when no match
mkdir -p /data/nginx/default_www # Default site files
mkdir -p /data/nginx/proxy_host # Your reverse proxy configurations
mkdir -p /data/nginx/redirection_host # URL redirects
mkdir -p /data/nginx/stream # TCP/UDP stream proxying
mkdir -p /data/nginx/dead_host # Disabled hosts
mkdir -p /data/nginx/temp # Temporary files
# Nginx cache directories for performance
mkdir -p /var/lib/nginx/cache/public # Public content cache
mkdir -p /var/lib/nginx/cache/private # Private content cache
mkdir -p /var/cache/nginx/proxy_temp # Temporary proxy cacheSet the appropriate permissions
Nginx needs full access to cache directory
chmod -R 777 /var/cache/nginxEnsure root owns the temp directory
chown root:root /tmp/nginxStep 11: Copy NPM Files to System
The docker/rootfs/ directory contains the complete filesystem that would be inside the Docker container. We need to extract and place these files in the correct locations on our system.
cp -r docker/rootfs/var/www/html/* /var/www/html/
cp -r docker/rootfs/etc/nginx/* /etc/nginx/
cp docker/rootfs/etc/letsencrypt.ini /etc/letsencrypt.ini
cp docker/rootfs/etc/logrotate.d/nginx-proxy-manager /etc/logrotate.d/nginx-proxy-managerCreate nginx configuration link
ln -sf /etc/nginx/nginx.conf /etc/nginx/conf/nginx.conf
rm -f /etc/nginx/conf.d/dev.confStep 12: Configure DNS Resolvers
Nginx needs to know which DNS servers to use when it proxies to domains (like when you proxy to “service.local” instead of an IP). This extracts your system’s DNS servers and formats them for nginx.
echo resolver “$(awk ‘BEGIN{ORS=” “} $1==”nameserver” {print ($2 ~ “:”)? “[”$2”]”: $2}’ /etc/resolv.conf);” \
> /etc/nginx/conf.d/include/resolvers.confBreaking down this command:
awk ‘BEGIN{ORS=” “}- Set Output Record Separator to space (instead of newline)$1==”nameserver”- Find lines starting with “nameserver”{print ($2 ~ “:”)? “[”$2”]”: $2}- If IPv6 (contains :), wrap in brackets, otherwise print as-isResult: Creates a line like
resolver 192.168.10.1 8.8.8.8;for nginx
Step 13: Generate Dummy SSL Certificate
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \
-subj “/O=Nginx Proxy Manager/OU=Dummy Certificate/CN=localhost” \
-keyout /data/nginx/dummykey.pem \
-out /data/nginx/dummycert.pemStep 14: Build Frontend
NPM’s web interface is a modern JavaScript application that needs to be “built” (compiled/bundled) from source code. The source includes placeholder version numbers (0.0.0) that need to be updated.
Update version information in package files
sed -i “s|\”version\”: \”0.0.0\”|\”version\”: \”$RELEASE\”|” backend/package.json
sed -i “s|\”version\”: \”0.0.0\”|\”version\”: \”$RELEASE\”|” frontend/package.jsonBuild it (can take a few minutes)
cd ./frontend
pnpm install
pnpm run build
cd ..What happens during build:
Transpiles modern JavaScript to browser-compatible code
Bundles all JS files into optimized chunks
Processes CSS and images
Creates the
dist/directory with production-ready files
Step 15: Install Application Files
Create application directory structure
mkdir -p /app/global /app/frontend/imagesCopy all application files to proper locations
cp -r frontend/dist/* /app/frontend/
cp -r frontend/app-images/* /app/frontend/images/
cp -r backend/* /app/
cp -r global/* /app/global/Step 16: Configure Database
cat <<’EOF’ >/app/config/production.json
{
“database”: {
“engine”: “knex-native”,
“knex”: {
“client”: “sqlite3”,
“connection”: {
“filename”: “/data/database.sqlite”
}
}
}
}
EOFStep 17: Install Backend Dependencies
cd /app
pnpm installStep 18: Create Systemd Service
Docker would normally manage the NPM process. Since we’re not using Docker, we need systemd (the Linux service manager) to start NPM on boot and restart it if it crashes.
Create NPM service file for systemd
cat <<’EOF’ >/lib/systemd/system/npm.service
[Unit]
Description=Nginx Proxy Manager
After=network.target
Wants=openresty.service
[Service]
Type=simple
Environment=NODE_ENV=production
# The - prefix means “ignore if this fails” (dirs might already exist)
ExecStartPre=-mkdir -p /tmp/nginx/body /data/letsencrypt-acme-challenge
# Start the Node.js application with memory limit and error handling
ExecStart=/usr/local/bin/node index.js --abort_on_uncaught_exception --max_old_space_size=250
WorkingDirectory=/app
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOFService file explanation:
After=network.target- Wait for network to be readyWants=openresty.service- Try to start OpenResty tooNODE_ENV=production- Tell Node.js to run in production mode--max_old_space_size=250- Limit Node.js memory to 250MBRestart=on-failure- Automatically restart if NPM crashes
Step 19: Configure Services for Container Environment
In Docker, the internal process typically runs as root (even if the container doesn’t have host root access). NPM expects this and needs root to:
Bind to ports 80/443
Write to various system directories
Manage nginx processes
Since we’re in an unprivileged LXC container, this “root” is actually restricted and safe.
Modify nginx to run as root (required in container)
sed -i ‘s/user npm/user root/g; s/^pid/#pid/g’ /usr/local/openresty/nginx/conf/nginx.confUpdate logrotate configuration
sed -i ‘s/su npm npm/su root root/g’ /etc/logrotate.d/nginx-proxy-managerReload systemd daemon to recognize new service
systemctl daemon-reloadStep 20: Start Services
Enable services to start on boot
systemctl enable openresty
systemctl enable npmStart the services
systemctl start openresty
systemctl start npmGive it about 5 seconds and then restart openresty to make sure it catches the config
systemctl reload openrestyStep 21: Verify Installation
Check service status
systemctl status openresty
systemctl status npmVerify NPM is listening on port 81
ss -tulnp | grep :81If you get nothing back - try reloading openresty one more time
systemctl reload openrestyCheck for errors in logs
journalctl -u npm --no-pager | tail -20Step 22: Clean Up
Remove temporary installation files
cd /
rm -rf /tmp/nginx-proxy-manager-*
rm -f /tmp/v2.12.6.tar.gz.tar.gzExit container
exitPost-Installation
Accessing Nginx Proxy Manager
The web interface should now be accessible at:
URL:
http://192.168.10.101:81
Default Email:
admin@example.comDefault Password:
changeme

