|
| 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 | +| | |
| 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 | +| | |
| 147 | + |
| 148 | +1. Click on the "SSL Certificates" tab <!--A-->🄰. |
| 149 | +2. Click <kbd>Add SSL Certificate</kbd> <!--B-->🄱 and choose one of the options <!--C-->🄲. |
| 150 | +3. If you are provisioning a Let's Encrypt certificate: |
| 151 | + |
| 152 | + - fill in the fields <!--D-->🄳 and <!--E-->🄴 |
| 153 | + - optionally enable the DNS challenge <!--F-->🄵 |
| 154 | + - agree to the Ts&Cs <!--G-->🄶 and |
| 155 | + - click <kbd>Save</kbd> <!--H-->🄷. |
| 156 | + |
| 157 | +4. If you are provisioning a self-signed certificate: |
| 158 | + |
| 159 | + - provide a name at <!--I-->🄸 (eg "*.your.home.arpa") |
| 160 | + - click <kbd>Browse</kbd> <!--J-->🄹, then navigate to and select the private key associated with your wildcard certificate |
| 161 | + - click <kbd>Browse</kbd> <!--K-->🄺, 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-->🄻. |
| 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 | +| | |
| 172 | + |
| 173 | +1. Click on "Hosts" <!--A-->🄰 and choose "Proxy Hosts" from the menu <!--B-->🄱. |
| 174 | +2. Click <kbd>Add Proxy Host</kbd> <!--C-->🄲. |
| 175 | +3. The "Details" tab <!--D-->🄳 should be selected by default but, if not, select it. |
| 176 | +4. In the "Domain Names" field <!--E-->🄴, 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-->🄵, "Forward Hostname or IP" <!--G-->🄶 and "Forward Port" fields <!--H-->🄷 should be completed. |
| 185 | + |
| 186 | + | <a name="table1"></a>Table 1: Nginx forwarding | |
| 187 | + |:-------------------------------------------------:| |
| 188 | + | | |
| 189 | + |
| 190 | + The choice of *scheme* <!--F-->🄵 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-->🄸. |
| 202 | +7. Switch to the "SSL" tab <!--J-->🄹. |
| 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-->🄵) you can also enable "Force SSL" <!--L-->🄻 to prevent attempts to use HTTP. |
| 205 | +10. Click <kbd>Save</kbd> <!--M-->🄼. |
| 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 | +``` |
0 commit comments