#!/usr/bin/env bash # ===================================================================== # Nginx Proxy Manager — native (no Docker) install on Debian 12 # --------------------------------------------------------------------- # Built 2026-10-03 for VM 116 edge-proxy-1 (192.168.12.52). Adapted from the # community-scripts ProxmoxVE recipe (tteck/CrazyWolf13, MIT) with two changes: # * OpenResty from its official apt repo (upgradable) instead of a source build # * certbot venv also gets certbot-dns-ionos (our DNS provider) for DNS-01 later # Idempotent enough to re-run; `NPM_RELEASE=v2.x.y` pins a version (default: latest). # # bash install-npm-baremetal.sh # full install # bash install-npm-baremetal.sh --upgrade # rebuild app from the latest release, keep /data # # Layout (same as the Docker image, so upstream docs apply): # /app backend + built frontend /data sqlite DB, generated nginx conf, custom certs # /etc/nginx -> /usr/local/openresty/nginx (conf.d from the NPM repo) # /etc/letsencrypt certbot state /opt/certbot certbot venv # Services: openresty.service, npm.service. UI: http://:81 (private networks only, nftables). # ===================================================================== set -euo pipefail export DEBIAN_FRONTEND=noninteractive MODE="${1:-install}" NPM_RELEASE="${NPM_RELEASE:-$(curl -fsSL https://api.github.com/repos/NginxProxyManager/nginx-proxy-manager/releases/latest | grep -oE '"tag_name": *"[^"]+"' | cut -d'"' -f4)}" RELEASE="${NPM_RELEASE#v}" echo "== Nginx Proxy Manager ${NPM_RELEASE} ($MODE)" if [ "$MODE" = "install" ]; then echo "== OS prep" apt-get -qq update apt-get -qq -y full-upgrade apt-get -qq -y install curl gnupg ca-certificates lsb-release apt-transport-https \ apache2-utils logrotate git sqlite3 nftables unattended-upgrades qemu-guest-agent \ python3 python3-dev python3-pip python3-venv python3-cffi build-essential libffi-dev libssl-dev timedatectl set-timezone Europe/Berlin systemctl enable -q --now qemu-guest-agent unattended-upgrades echo "== OpenResty (official apt repo)" curl -fsSL https://openresty.org/package/pubkey.gpg | gpg --dearmor -o /usr/share/keyrings/openresty.gpg echo "deb [signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package/debian $(lsb_release -sc) openresty" >/etc/apt/sources.list.d/openresty.list apt-get -qq update apt-get -qq -y install openresty systemctl disable -q --now openresty 2>/dev/null || true # replaced by our unit below echo "== certbot venv" python3 -m venv /opt/certbot /opt/certbot/bin/pip -q install --upgrade pip setuptools wheel /opt/certbot/bin/pip -q install certbot certbot-dns-ionos certbot-dns-cloudflare ln -sf /opt/certbot/bin/certbot /usr/local/bin/certbot echo "== Node.js 22 + yarn" curl -fsSL https://deb.nodesource.com/setup_22.x | bash - >/dev/null apt-get -qq -y install nodejs npm install -g --silent yarn fi echo "== fetch NPM ${NPM_RELEASE}" rm -rf /opt/nginxproxymanager && mkdir -p /opt/nginxproxymanager curl -fsSL "https://github.com/NginxProxyManager/nginx-proxy-manager/archive/refs/tags/${NPM_RELEASE}.tar.gz" \ | tar -xz --strip-components=1 -C /opt/nginxproxymanager echo "== environment" ln -sf /usr/bin/python3 /usr/bin/python ln -sf /usr/local/openresty/nginx/sbin/nginx /usr/sbin/nginx [ -e /etc/nginx ] && [ ! -L /etc/nginx ] && mv /etc/nginx /etc/nginx.dist-$(date +%s) ln -sfn /usr/local/openresty/nginx/ /etc/nginx sed -i "0,/\"version\": \"[^\"]*\"/s|\"version\": \"[^\"]*\"|\"version\": \"$RELEASE\"|" /opt/nginxproxymanager/backend/package.json sed -i "0,/\"version\": \"[^\"]*\"/s|\"version\": \"[^\"]*\"|\"version\": \"$RELEASE\"|" /opt/nginxproxymanager/frontend/package.json sed -i 's+^daemon+#daemon+g' /opt/nginxproxymanager/docker/rootfs/etc/nginx/nginx.conf find /opt/nginxproxymanager -type f -name "*.conf" -exec sed -i 's+include conf.d+include /etc/nginx/conf.d+g' {} + mkdir -p /var/www/html /etc/nginx/logs cp -r /opt/nginxproxymanager/docker/rootfs/var/www/html/* /var/www/html/ cp -r /opt/nginxproxymanager/docker/rootfs/etc/nginx/* /etc/nginx/ cp /opt/nginxproxymanager/docker/rootfs/etc/letsencrypt.ini /etc/letsencrypt.ini cp /opt/nginxproxymanager/docker/rootfs/etc/logrotate.d/nginx-proxy-manager /etc/logrotate.d/nginx-proxy-manager ln -sf /etc/nginx/nginx.conf /etc/nginx/conf/nginx.conf rm -f /etc/nginx/conf.d/dev.conf [ -f /etc/nginx/conf.d/production.conf.template ] && sed 's/{{NPM_ADMIN_PORT}}/81/g' /etc/nginx/conf.d/production.conf.template >/etc/nginx/conf.d/production.conf mkdir -p /tmp/nginx/body /run/nginx /data/nginx /data/custom_ssl /data/logs /data/access \ /data/nginx/default_host /data/nginx/default_www /data/nginx/proxy_host /data/nginx/redirection_host \ /data/nginx/stream /data/nginx/dead_host /data/nginx/temp /data/letsencrypt-acme-challenge \ /var/lib/nginx/cache/public /var/lib/nginx/cache/private /var/cache/nginx/proxy_temp /etc/letsencrypt chmod -R 777 /var/cache/nginx echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" {print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf);" >/etc/nginx/conf.d/include/resolvers.conf if [ ! -f /data/nginx/dummycert.pem ]; then 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.pem 2>/dev/null fi echo "== build frontend" rm -rf /app && mkdir -p /app/frontend/images cp -r /opt/nginxproxymanager/backend/* /app export NODE_OPTIONS="--max_old_space_size=2048 --openssl-legacy-provider" cd /opt/nginxproxymanager/frontend sed -E -i 's/"node-sass" *: *"([^"]*)"/"sass": "\1"/g' package.json yarn install --network-timeout 600000 --silent yarn locale-compile >/dev/null 2>&1 || true yarn build >/dev/null yarn cache clean >/dev/null cp -r dist/* /app/frontend cp -r public/images/* /app/frontend/images echo "== backend" rm -f /app/config/default.json mkdir -p /app/config [ -f /app/config/production.json ] || cat >/app/config/production.json <<'JSON' { "database": { "engine": "knex-native", "knex": { "client": "better-sqlite3", "connection": { "filename": "/data/database.sqlite" }, "useNullAsDefault": true } } } JSON cd /app && yarn install --network-timeout 600000 --silent && yarn cache clean >/dev/null echo "== services" cat >/lib/systemd/system/openresty.service <<'UNIT' [Unit] Description=OpenResty (nginx) for Nginx Proxy Manager After=network-online.target nss-lookup.target Wants=network-online.target [Service] Type=simple ExecStartPre=-/bin/mkdir -p /tmp/nginx/body /run/nginx ExecStartPre=/usr/local/openresty/nginx/sbin/nginx -t ExecStart=/usr/local/openresty/nginx/sbin/nginx -g 'daemon off;' ExecReload=/usr/local/openresty/nginx/sbin/nginx -s reload Restart=on-failure [Install] WantedBy=multi-user.target UNIT CERTBOT_VER=$(/opt/certbot/bin/certbot --version 2>&1 | awk '{print $NF}') cat >/lib/systemd/system/npm.service </etc/nftables.conf <<'NFT' #!/usr/sbin/nft -f flush ruleset table inet filter { chain input { type filter hook input priority 0; policy accept; iif lo accept tcp dport 81 ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } accept tcp dport 81 drop } } NFT systemctl enable -q --now nftables && nft -f /etc/nftables.conf fi systemctl daemon-reload systemctl enable -q openresty npm systemctl restart openresty systemctl restart npm sleep 8 echo "== status" systemctl is-active openresty npm | paste - - curl -s -o /dev/null -w 'UI :81 -> %{http_code}\n' http://127.0.0.1:81/ curl -s -o /dev/null -w 'default :80 -> %{http_code}\n' http://127.0.0.1:80/ echo "DONE. First login: admin@example.com / changeme at http://$(hostname -I | awk '{print $1}'):81"