Chapter 1 of the dmz-web documentation · headings are numbered like the cheat sheets · index: Wiki
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.
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.
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):
[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:
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: dmz-web-podman-cheatsheet.md v1.2 in the dmz-web-deploy repo (Releases). Edit the wiki and the repo copy together.
Updated by Redmine Admin 6 days ago · 3 revisions