Project

General

Profile

NPM » History » Revision 2

Revision 1 (Redmine Admin, 10/25/2026 04:25 PM) → Revision 2/3 (Redmine Admin, 10/26/2026 02:24 AM)

# Nginx Proxy Manager + Let's Encrypt 

 Unit: `nginx-proxy-manager.container`. Published on **host 81 → 80, 444 → 443** 
   

 (rootless can't bind <1024); admin UI on **:8181**, reachable only from the 
 subnets allowed by firewalld rich rules (`firewall-cmd --list-rich-rules`, root): 
 10.202.10.0/24 (VB21 = home LAN, own FortiGate, S2S to HQ), 192.168.100.0/24 
 (MDS-HQ office LAN), 172.16.103.0/24 (the host's own DMZ segment). Deliberately NOT the SSL-VPN pool (10.21.0.0/24): the 
 host rule must not depend on who the FortiGate let onto the VPN; from the road use 
 the SSH tunnel. A new admin subnet = one more rich rule 
 (`--permanent --add-rich-rule='rule family=ipv4 source address=<net>/24 port port=8181 protocol=tcp accept'` + `--reload`). 
 Fallback from anywhere SSH works: `ssh -L 8181:127.0.0.1:8181 root@dmz-web` → `http://localhost:8181`. 
 **:8181, VPN only**. The FortiGate VIP 
   

 does the public **443 → 444** translation. No hairpin NAT, so from inside the 
   

 public names resolve to the WAN IP and don't work — see [[Health Check]]. 

 [Health Check](#). 

 NPM must be a member of every site's network to reach `site-<name>:80` by 
   

 container name. Each new site adds three lines to the unit, above the 
   

 `# --- next site: …` marker comments (`add-wordpress-site.sh` does this 
   

 automatically; for Redmine/Snipe-IT do it by hand): 

 ```ini 
 

 ``` 
 [Unit] 
 
 After=net-<name>-network.service 
 
 Requires=net-<name>-network.service 
 
 [Container] 
 
 Network=net-<name>.network 
 
 ``` 

 

 then `cp` to `~/.config/containers/systemd/`, `daemon-reload`, `restart nginx-proxy-manager.service`. 

 **Before any `cp` of this unit: the repo copy must contain every `Network=` line 
 the live copy has.** On 2026-10-25 `snipeit-install.sh` copied a repo version that 
 only knew `net-snipeit` over the live unit → NPM left the other three networks → 
 every site 502 until the lines were re-added. Check with 
 `diff <(grep '^Network=' ~/.config/containers/systemd/nginx-proxy-manager.container) <(grep '^Network=' ~/deploy/quadlet/nginx-proxy-manager.container)` 
 (empty = safe). Restarting NPM drops every site for ~10–20 s — all three install scripts do it. 

 

 Proxy host (UI or API): Domain `<domain>` → scheme http, Forward Hostname 
   

 `site-<name>`, Forward Port 80 (Redmine: 3000), Block Common Exploits, 
   

 Websockets; SSL tab → Let's Encrypt, Force SSL, HTTP/2. 
   

 LE needs DNS A record + FortiGate VIP live first (HTTP-01 challenge comes in 
   

 over 80 → 81). The LE contact email is the NPM *user account's* email, not a 
   

 per-cert setting. 

 

 API credentials are Podman secrets `npm-admin-identity` / `npm-admin-secret`. 
   

 Quick API test: 

 ```bash 
 

 ``` 
 curl -s http://localhost:8181/api/ | head -c 200 
 
 podman network inspect net-<name> | jq '.[0].containers | keys'     # must list nginx-proxy-manager 
 
 ``` 

 

 Restrict an internal-only app (Snipe-IT, Redmine admin): NPM **Access List** 
   

 with the VPN/office ranges attached to that proxy host. 

 

 --- 
 _Source: 

 *Source: `dmz-web-podman-cheatsheet.md` v1.2 v1.1.1 in the `dmz-web-deploy` repo ([[Releases]]). ([Releases](#)). Edit the wiki and the repo copy together._ 
 together.*