Skip to content

Commit 6865efa

Browse files
committed
2025-05-27 nginx - master branch - PR 1 of 2
Adds Nginx template and documentation. I do not know whether it is possible for a later PR to close an earlier PR, in the same way that a PR can mark an issue for closure. Neither do I know whether it is appropriate GitHub etiquette to even try. I will simply say that, in my view, this PR supersedes any need for SensorsIot#638 and that, providing @enriquedelpino (the creator) and @robertcsakany (who made several contributions) do not object, I recommend SensorsIot#638 be closed. The Nginx container being proposed in this PR is a self-contained all-in-one solution. It is a single template and does not touch any other service definitions. That compares/contrasts with SensorsIot#638 which was spread across three service definitions, touched 15 existing service definitions and, I infer, would have implied similar changes to the service definitions of any "proxyable" (if that's a word) containers added subsequently. I have been testing the `jc21/nginx-proxy-manager` for the past couple of months. I won't claim to have given it a full workout because my testing has been limited to self-signed SSL certificates (ie no Let's Encrypt) and I have only defined "proxy hosts" (ie no "redirection hosts", "streams" or "404 hosts", and no "access lists"). The proxy hosts that I have defined include a judicious mix of HTTP and HTTPS services, running on the same and different hosts, and running in both host mode and non-host mode. I have also tested in conjunction with CNAME records defined by both PiHole and BIND9. The Nginx service as implemented by the `jc21` Docker image works and is reliable. The only problems I have found are: 1. A situation where obsolete private SSL certificates are not removed from the database when they are deleted. This was filed as [Issue 4442](NginxProxyManager/nginx-proxy-manager#4442). 2. The procedure for the "forgot password" use case is not exactly well documented. For example it's buried in places like [Issue 230](NginxProxyManager/nginx-proxy-manager#230). It's also a little bit coarse in that it kills **all** user records. Granted, in most IOTstack environments there will only be one user anyway but it's still poor practice in an SQL sense and I'd rather not perpetuate it. The documentation included with this PR adopts the approach of resetting the password of the problematic account to a known value. Signed-off-by: Phill Kelley <[email protected]>
1 parent 940f96b commit 6865efa

File tree

6 files changed

+302
-0
lines changed

6 files changed

+302
-0
lines changed

.templates/nginx/service.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
nginx:
2+
container_name: nginx
3+
image: jc21/nginx-proxy-manager:latest
4+
restart: unless-stopped
5+
environment:
6+
- TZ=${TZ:-Etc/UTC}
7+
8+
- INITIAL_ADMIN_PASSWORD=changeme
9+
- DISABLE_IPV6=true
10+
extra_hosts:
11+
- "host.docker.internal:host-gateway"
12+
ports:
13+
- "80:80" # HTTP reverse proxy service
14+
- "81:81" # HTTP admin console
15+
- "443:443" # HTTPS reverse proxy service
16+
volumes:
17+
- ./volumes/nginx/data:/data
18+
- ./volumes/nginx/letsencrypt:/etc/letsencrypt
19+
- /var/run/docker.sock:/var/run/docker.sock

docs/Containers/Nginx.md

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
# Nginx
2+
3+
Nginx is a reverse proxy service. From the perspective of a client (browser), an Nginx server appears to be a standard web server but its behaviour is that of an intermediary, relaying client requests to the web service the client *actually* wants to reach.
4+
5+
## references
6+
7+
* Nginx:
8+
9+
- [Documentation](https://nginxproxymanager.com)
10+
- [Dockerhub](https://hub.docker.com/r/jc21/nginx-proxy-manager)
11+
- [GitHub](https://github.com/NginxProxyManager/nginx-proxy-manager)
12+
13+
* [Let's Encrypt](https://letsencrypt.org)
14+
15+
* [Creating private self-signed SSL certificates](https://github.com/Paraphraser/ssl-certificates)
16+
17+
## default ports
18+
19+
The IOTstack implementation listens on the following ports:
20+
21+
* `80` the common front end for HTTP-based traffic
22+
* `443` the common front end for HTTPS-based traffic
23+
* `81` the administrative interface for Nginx
24+
25+
> these are all "privileged" ports which means you may have trouble deploying this container in a "Docker Desktop" environment.
26+
27+
## environment variables
28+
29+
You can expect Nginx to work out of the box without any special configuration. The IOTstack service definition includes these environment variables:
30+
31+
``` yaml
32+
environment:
33+
- TZ=${TZ:-Etc/UTC}
34+
35+
- INITIAL_ADMIN_PASSWORD=changeme
36+
- DISABLE_IPV6=true
37+
```
38+
39+
Although you can change them if you wish, the email address and password only have effect on first launch, and only up until your first login when you are guided through the process of defining the first user and a (hopefully) stronger password.
40+
41+
Unless you have good reasons for enabling IPv6, it is recommended that you leave it disabled.
42+
43+
## reference model
44+
45+
The network model shown in [Figure 1](#figure1) will help you to understand how to configure Nginx.
46+
47+
| <a name="figure1"></a>Figure 1: Network Model |
48+
|:-------------------------------------------------------------:|
49+
|![Network reference model](./images/nginx-reference-model.png) |
50+
51+
The model consists of two hosts, `pi1` and `pi2`. each of which is running three Docker containers. This model was constructed to show how you would set up Nginx to reach services running under various conditions. All these services could just as easily be running on a single computer and, rather than being hosted on physical computers (eg Raspberry Pi), the hosts could be implemented as a virtual guests in a Proxmox-VE system.
52+
53+
## domain name service
54+
55+
You **will** need a Domain Name System service to make effective use of Nginx. You can use implementations like Pi-hole, BIND9 (Berkeley Internet Name Daemon - the grandparent of all DNS servers) or any other equivalent service. Your DNS service can be native or running in a container. It can be private, public, on-site or off-site.
56+
57+
This document uses the domain `your.home.arpa` throughout. Substiute your own domain accordingly. This guide assumes a DNS service which can resolve the following resource records:
58+
59+
```
60+
$ORIGIN your.home.arpa.
61+
62+
pi1 IN A 192.168.203.60
63+
pi2 IN A 192.168.203.61
64+
65+
esphome IN CNAME pi1.your.home.arpa.
66+
gitea IN CNAME pi1.your.home.arpa.
67+
grafana IN CNAME pi1.your.home.arpa.
68+
nginx IN CNAME pi1.your.home.arpa.
69+
nodered IN CNAME pi1.your.home.arpa.
70+
pihole IN CNAME pi1.your.home.arpa.
71+
```
72+
73+
The above list uses BIND9 syntax. In words:
74+
75+
* all the names in the left-hand column imply `your.home.arpa`. For example:
76+
77+
- `pi1` implies `pi1.your.home.arpa`.
78+
79+
* `pi1` and `pi2` are actual hosts which are reachable at the associated IP addresses (the "A" means "address"). For example:
80+
81+
- `pi1.your.home.arpa` is reachable at 192.168.203.60
82+
83+
* the remaining names are aliases. That is, when presented with a domain name which maps to a CNAME record, the DNS also looks-up the corresponding A record. For example:
84+
85+
- `esphome.your.home.arpa` looks up `pi1.your.home.arpa` and returns 192.168.203.60
86+
87+
In terms of Pi-hole:
88+
89+
* for versions 2024.07.0 and earlier, you manage:
90+
91+
- `A` records via `Local DNS` » `DNS Records`; and
92+
- `CNAME` records via `Local DNS` » `CNAME Records`
93+
94+
* for later versions, `Settings` » `Local DNS Records` manages both `A` and `CNAME` records.
95+
96+
If you use another DNS server you will need to work it out for yourself.
97+
98+
## reverse proxy basics
99+
100+
When you type a URL like either of the following into the search bar of your browser:
101+
102+
```
103+
http://esphome.your.home.arpa
104+
https://esphome.your.home.arpa
105+
```
106+
107+
1. The default port of 80 is assumed (for HTTP) or 443 (for HTTPS);
108+
2. Via the `CNAME` lookup, the DNS supplies the IP adddress of the host `pi1` (192.168.203.60) so your client's system can direct IP datagrams to the host where the Nginx service is running;
109+
3. The Nginx service listening on port 80 (or 443) on `pi1` still sees the original URL of:
110+
111+
```
112+
http://esphome.your.home.arpa
113+
```
114+
115+
and can use the **string** `esphome.your.home.arpa` to work out how to redirect the connection to the `esphome` **service** which, in this example, is running on the same host and is listening on port 6052.
116+
117+
## getting started
118+
119+
Nginx uses port 81 for its administrative interface. To manage Nginx you connect to the host where the Nginx service is running using port 81. For example:
120+
121+
```
122+
http://pi1.your.home.arpa:81
123+
```
124+
125+
Login with the default credentials:
126+
127+
* Email address: `[email protected]`
128+
* Password: `changeme`
129+
130+
You will be presented with an "Edit User" dialog where you should set the fields according to your requirements. That dialog is followed by a "Change Password" dialog where you enter `changeme` into the "Current Password" field, then supply a new password of appropriate strength.
131+
132+
See also [if you forget your password](#forgotPassword).
133+
134+
## add SSL certificate { #addCert }
135+
136+
Although it is not essential, it is a good idea to provision at least one SSL certificate before you start to use the Nginx reverse proxy manager. This is usually a so-called *wildcard* certificate, meaning it will authenticate hosts matching `*.your.home.arpa`. Nginx supports both [Let's Encrypt](https://letsencrypt.org) and private *self-signed* SSL certificates:
137+
138+
* The exact mechanisms for obtaining certificates from [Let's Encrypt](https://letsencrypt.org) are beyond the scope of this documentation. Google is your friend.
139+
140+
* See [Creating private self-signed SSL certificates](https://github.com/Paraphraser/ssl-certificates) for a guide to setting up and deploying a scheme of self-signed SSL certificates.
141+
142+
Once you have decided on your approach, use [Figure 2](#figure2) as your guide.
143+
144+
| <a name="figure2"></a>Figure 2: Add SSL certificate |
145+
|:------------------------------------------------------:|
146+
|![Add SSL certificate](./images/nginx-add-ssl-cert.png) |
147+
148+
1. Click on the "SSL Certificates" tab <!--A-->&#x1F130;.
149+
2. Click <kbd>Add SSL Certificate</kbd> <!--B-->&#x1F131; and choose one of the options <!--C-->&#x1F132;.
150+
3. If you are provisioning a Let's Encrypt certificate:
151+
152+
- fill in the fields <!--D-->&#x1F133; and <!--E-->&#x1F134;
153+
- optionally enable the DNS challenge <!--F-->&#x1F135;
154+
- agree to the Ts&Cs <!--G-->&#x1F136; and
155+
- click <kbd>Save</kbd> <!--H-->&#x1F137;.
156+
157+
4. If you are provisioning a self-signed certificate:
158+
159+
- provide a name at <!--I-->&#x1F138; (eg "*.your.home.arpa")
160+
- click <kbd>Browse</kbd> <!--J-->&#x1F139;, then navigate to and select the private key associated with your wildcard certificate
161+
- click <kbd>Browse</kbd> <!--K-->&#x1F13A;, then navigate to and select your wildcard certificate
162+
- in most home networks you won't need to provide an intermediate certificate so you can leave that empty; and
163+
- click <kbd>Save</kbd> <!--L-->&#x1F13B;.
164+
165+
## adding hosts
166+
167+
Use [Figure 3](#figure3) as your guide.
168+
169+
| <a name="figure3"></a>Figure 3: Add proxy host |
170+
|:---------------------------------------------------:|
171+
|![Add proxy host](./images/nginx-add-proxy-host.png) |
172+
173+
1. Click on "Hosts" <!--A-->&#x1F130; and choose "Proxy Hosts" from the menu <!--B-->&#x1F131;.
174+
2. Click <kbd>Add Proxy Host</kbd> <!--C-->&#x1F132;.
175+
3. The "Details" tab <!--D-->&#x1F133; should be selected by default but, if not, select it.
176+
4. In the "Domain Names" field <!--E-->&#x1F134;, type the fully-qualified **alias** (or "CNAME") name of the service. For example:
177+
178+
```
179+
esphome.your.home.arpa
180+
```
181+
182+
> There is a small trick to this. As you type the characters of the domain name, the name will be repeated in a drop-down menu below the field. Once you have finished typing the domain name *in* the field, click on the completed domain name in the drop-down menu *below* the field.
183+
184+
5. [Table 1](#table1) shows how the "Scheme" <!--F-->&#x1F135;, "Forward Hostname or IP" <!--G-->&#x1F136; and "Forward Port" fields <!--H-->&#x1F137; should be completed.
185+
186+
| <a name="table1"></a>Table 1: Nginx forwarding |
187+
|:-------------------------------------------------:|
188+
|![Nginx forwarding](./images/nginx-forwarding.png) |
189+
190+
The choice of *scheme* <!--F-->&#x1F135; depends on whether the *service* you are trying to provision expects HTTP or HTTPS. In the [reference model](#figure1), only `gitea` expects HTTPS; all the others expect HTTP.
191+
192+
The general rules are:
193+
194+
1. If Nginx is trying to reach itself, use `localhost` or 127.0.0.1 or the container name (`nginx`) and the internal (container) port where the administrative GUI is to be found.
195+
2. Nginx is a non-host-mode Docker container so if it is trying to reach:
196+
197+
- another non-host-mode service on the *same* host then use the `«containerName»` and internal port. The example here is Node-RED.
198+
- a host-mode or native service on the *same* host then use the special name `host.docker.internal` and the host port on which the service is listening. The example here is ESPHome.
199+
- a service on a *different* host then use the fully-qualified domain name of that other host plus the external port (if the service is running in a non-host-mode container) or host port (if the service is either native or running in a host-mode container).
200+
201+
6. In almost all cases, you will want to enable "Websockets Support" <!--I-->&#x1F138;.
202+
7. Switch to the "SSL" tab <!--J-->&#x1F139;.
203+
8. The "SSL Certificate" field is a popup menu. Choose the relevant (or only) certificate you [provisioned earlier](#addCert).
204+
9. If the destination service expects HTTPS traffic (ie the choice you made at <!--F-->&#x1F135;) you can also enable "Force SSL" <!--L-->&#x1F13B; to prevent attempts to use HTTP.
205+
10. Click <kbd>Save</kbd> <!--M-->&#x1F13C;.
206+
207+
Each proxy host that you provision appears in the list. You can test connections locally by clicking on the service name in the "Source" column.
208+
209+
## container maintenance
210+
211+
You can maintain the Nginx container with normal `pull` commands:
212+
213+
``` console
214+
$ cd ~/IOTstack
215+
$ docker compose pull nginx
216+
$ docker compose up -d nginx
217+
$ docker system prune -f
218+
```
219+
220+
## if you forget your password { #forgotPassword }
221+
222+
If you forget your password, you really only have two choices:
223+
224+
1. You can erase the persistent store and start from a [clean slate](#cleanSlate); or
225+
2. You can attempt to replace your lost password with a known value.
226+
227+
To replace your password with a known value:
228+
229+
1. You will need the `whois` package:
230+
231+
``` console
232+
$ sudo apt update && sudo apt install -y whois
233+
```
234+
235+
2. Define the following variables:
236+
237+
``` console
238+
DB="$HOME/IOTstack/volumes/nginx/data/database.sqlite"
239+
EMAIL="user@domain"
240+
```
241+
242+
where `user@domain` is the email address associated with the password you have forgotten. If you have also forgotten the email address, you can recover a list of email addresses by running:
243+
244+
``` console
245+
$ sqlite3 "$DB" "SELECT email FROM user;"
246+
```
247+
248+
3. The `whois` package provides the `mkpasswd` utility, which you will use to generate a hash for your desired password:
249+
250+
``` console
251+
$ HASH=$(mkpasswd -m bcrypt)
252+
Password:
253+
```
254+
255+
Type your desired password at the `Password:` prompt and press <kbd>return</kbd>. The hashed version of your password will be copied to the `HASH` environment variable.
256+
257+
4. Run the following command:
258+
259+
``` console
260+
$ sudo sqlite3 "$DB" "UPDATE auth SET secret=\"$HASH\" WHERE user_id=(SELECT id FROM user WHERE email = \"$EMAIL\");"
261+
```
262+
263+
5. Restart the container:
264+
265+
``` console
266+
$ cd ~/IOTstack
267+
$ docker compose restart nginx
268+
```
269+
270+
Providing you don't make any mistakes, the database will be updated with the hash corresponding with the desired password you typed in step 3.
271+
272+
## starting over from a clean slate { #cleanSlate }
273+
274+
Starting from a clean slate means you lose all the certificates and hosts you have provisioned. You will need to set up everything again from scratch.
275+
276+
Proceed as follows:
277+
278+
``` console
279+
$ cd ~/IOTstack
280+
$ docker compose down nginx
281+
$ sudo rm -rf ./volumes/nginx
282+
$ docker compose up -d nginx
283+
```
269 KB
Loading
293 KB
Loading
83.5 KB
Loading
130 KB
Loading

0 commit comments

Comments
 (0)