Changes for page How to setup an Nginx reverse proxy and also provide a global X.509 certificate for it
Last modified by Alexandru Pentilescu on 2023/06/25 18:53
From version 6.1
edited by Alexandru Pentilescu
on 2022/06/11 21:30
on 2022/06/11 21:30
Change comment:
There is no comment for this version
To version 10.1
edited by Alexandru Pentilescu
on 2022/06/11 22:55
on 2022/06/11 22:55
Change comment:
There is no comment for this version
Summary
-
Page properties (1 modified, 0 added, 0 removed)
Details
- Page properties
-
- Content
-
... ... @@ -136,7 +136,7 @@ 136 136 Well, yes, this is an issue. But that's why there's more to show you, in the next section! 137 137 138 138 139 -= Setting up HTTP->HTTPS automatic redirecting and configuring a global X.509 certificate=139 += Setting up HTTP->HTTPS automatic redirecting = 140 140 141 141 Time to get into the groove of things! 142 142 First, we shall create a new configuration file in "/etc/nginx/sites-available" and then a symbolic link towards it in "/etc/nginx/sites-enabled" called "fallback.conf". Why? ... ... @@ -144,3 +144,146 @@ 144 144 This is considered good practice, because, you're eseentially redirecting the browser to use a more secured connection protocol. Not only will this ensure that any login credentials are protected from being stolen, but also that the exact resources being retrieved from our server are obfuscated, so that our visitors can browse our server privately. 145 145 146 146 The contents of this "fallback.conf" file are as follows: 147 + 148 +{{code language="nginx"}} 149 +server { 150 + listen [::]:443 ssl http2 default_server; # managed by Cert 151 + listen 443 default_server; 152 + server_name _; 153 + include /etc/nginx/snippets/ssl.conf; 154 + return 404; 155 +} 156 + 157 +server { 158 + server_name pentilescu.com; 159 + return 301 https://alexandru.pentilescu.com$request_uri; 160 + 161 + listen 80; 162 + listen [::]:80; 163 + listen 443 ssl; 164 + listen [::]:443; 165 +} 166 + 167 +server { 168 + server_name _; 169 + return 301 https://$host$request_uri; # managed by Certbot 170 + 171 + 172 + # return 301 https://$host$request_uri; 173 + 174 + listen 80 default_server; 175 + listen [::]:80 default_server; 176 +} 177 +{{/code}} 178 + 179 +Each of these 3 server blocks have a well defined purpose. Let's break them down! 180 + 181 +The most important thing to remember is how Nginx does request matching. 182 + 183 +To put it simply, you can have as many ".conf" files in the "sites-enabled" directory as you wish to have. And any one ".conf" file can contain multiple "server" directives, each with its own proxy_pass destination and with its own subdomain service name. 184 + 185 +So, when a new HTTP/HTTPS request arrives at our Nginx server, how does it decide which "server" directive to match with that specific request, so that it knows where to redirect it to? 186 + 187 +The answer is fairly easy: for each request, Nginx looks through all of the ".conf" files under "sites-enabled", looks through each ".conf" file's "server" directives while taking into account its "server_name" and "listen" directives and then it decides to match those which are the most specific to our request's parameters. 188 + 189 +To put it as simply as possible, suppose the aforementioned bitwarden.conf file is created and there's also the fallback.conf file as with the previous contents, as well. 190 + 191 +When a new request arrives to our server, destined to passwords.pentilescu.com port 443, our server will look at both of these ".conf" files. Let's suppose it starts looking with the fallback.conf file. 192 +It sees 3 "server" blocks in fallback.conf, the first of which matches for any incoming connections for port 443 (which matches our request) and matches for literally any server name (that's what the "_" after the "server" directive means). Since it matches for any server name, this is not a very specific match. Nginx will remember this as the closest match it has for now and then continue to look for other "server" blocks as well. 193 + 194 +The second "server" block only matches for requests directed at "pentilescu.com", not for any of its subdomains. As the request was made specifically to passwords.pentilescu.com, which is a subdomain of pentilescu.com, this does not match with this block at all. Nginx continues. 195 + 196 +The third "server" block matches with the server_name directive (as the server name is the "_" wildcard once again). However, this matches only for incoming connections coming to port 80. Our request is to port 443 though. As such, this match fails and Nginx will continue to look for more "server" directives. 197 + 198 +Then, Nginx will also take a look at the bitwarden.conf file. 199 + 200 +Here, there is one single "server" block, one which matches exactly with the "passwords.pentilescu.com" server name (as well as the 443 port). As this one has an exact match to both server_name and port, this is the most specific one to match. 201 + 202 +As such, Nginx will resolve to use that server block for this request and redirect it to that server's port. As such, the configuration from bitwarden.conf won over the configurations in fallback.conf. 203 + 204 +Simple, right? 205 + 206 +Now, you might wonder, what's the purpose of the first server block in fallback.conf, then. Well, fallback.conf should contain, ideally, resolution requests for stuff that doesn't match anything that's specific. 207 + 208 +For example, suppose the aforementioned setup, but with a request for "abcdefg.pentilescu.com" port 443. Where should such a request go to? 209 + 210 +Well, it doesn't match the "passwords.pentilescu.com" server_name block in bitwarden.conf. Nor does it match the "pentilescu.com" server block either, as it's a subdomain for pentilescu.com. Finally, it doesn't match to the third "server" directive in fallback.conf either, as that is destined only for connections incoming to port 80, not 443. The only matching candidate, as such, is the first "server" block in fallback.conf, which resolves to any name whatsoever, due to its wildcard server_name. In this situation, Nginx will just take it, as it's the only candidate. It also has the "default server" descriptor for port 443, which means that, even if the server name parameter didn't match, this would have still been the "server" block to handle it. The action detailed for this "server" block is "return 404", which tells Nginx to simply return a 404 status code, immediately. The browser will then report this "404" status code to the visitor, letting him know that the service he/she was attempting to access does not exist on this server, an indication that their request was malformed. 211 + 212 +This block effectively handles all malformed requests or any request that does not have a specific resolver for it. 213 + 214 +The second "server" block in fallback.conf is specifically for all requests coming to "pentilescu.com", not any of its subdomains. It listens to both ports 80 and 443 and, when it matches, it will return an HTTP code 301 to "https://alexandru.pentilescu.com$request_uri", effectively preserving the exact same URI that was used in the original request but simply redirecting it forcefully to the "alexandru.pentilescu,com", so that any requests to "pentilescu.com" will redirect the browser to "alexandru.pentilescu.com". Basically, this is a forced redirection so that, when accessing the naked domain, one would forcefully be redirected to a specific subdomain. This is just a quirck of my own website. You may choose not to include this behavior in your own platform. 215 + 216 +Finally, the third and last "server" block in our fallback.conf matches to literally any server name due to its wildcard and is the default server for port 80. Being the default server, this means this block will handle all requests coming from port 80 to our server that doesn't have a match. The action of this block is to return a 301 HTTP code to "https://$host$request_uri". "https://$host$request_uri" will resolve to the exact same URL as the original requested one, except that it has the "https://" prefix before it, instead. This means that we're telling Nginx that, for any request that doesn't have a better match to it coming from port 80 (i.e. through the HTTP protocol) we must send back a HTTP response code of 301 (i.e. redirect to) to redirect our visitor to the exact same URL that they already used but with the "https://" prefix appended, effectively forcing them to use port 443 instead. This will move all their requests from HTTP to HTTPS to automatically secure them with encryption! 217 + 218 +And, moreover, if we don't add any more "server" directives in any other one of our ".conf" files for Nginx, this block will always be used for incoming port 80 requests, so that nothing more specific resolves for them and we can handle all of them by redirecting them to use a more secure protocol. 219 + 220 +Pretty cool, right? 221 + 222 +Finally, let's see how we can configure an X509 certificate globally! 223 + 224 + 225 += Configuring a global X.509 certificate = 226 + 227 +This is the easiest part of this article. Whenever you wish to encrypt a request to a specific server block in Nginx, just add the "include /etc/nginx/snippets/ssl.conf" directive in its server block and you're pretty much done. 228 +Now, what should this ssl.conf snippets file contain? Easy: 229 + 230 +{{code language="nginx"}} 231 +ssl_certificate /etc/letsencrypt/live/pentilescu.com/fullchain.pem; # managed by Certbot 232 +ssl_certificate_key /etc/letsencrypt/live/pentilescu.com/privkey.pem; # managed by Certbot 233 +ssl_trusted_certificate /etc/letsencrypt/live/pentilescu.com/chain.pem; 234 +{{/code}} 235 + 236 +Now, I admit, these file paths are usually generated by the certbot utility. Configuring certbox is outside the scope of this article and I will not cover it. 237 +certbot is also an utility specific for the Let's Encrypt CA, which might differ from your own certificate authority. But, regardless of which CA you choose to use, everything should boil down to 3 ".pem" files at the end, one containing your public key that will be delived to the visitor, one containing the fullchain and one containing the private key which will be used by Nginx to decrypt incoming traffic with. 238 + 239 +Technically, the ssl_certificate_key should point to your private key file. DO NOT, UNDER ANY CIRCUMSTANCES, GIVE THIS TO ANYONE. This has to be kept private and only you and Nginx should have access to it. 240 + 241 +chain.pem contains your public certificate along with the signature from your certificate authority proving its validity. 242 + 243 +fullchain.pem contains everything that chain.pem contains, plus the certificate's authority's own public certificate, signed by a root certificate authority, one that should be recognized by any visitor's web browser. 244 + 245 +As such, please change these file paths to the 3 files that you will be using from your respective CA. If in doubt, always ask for professional help from a sysadmin! 246 + 247 += Testing our setup and deploying = 248 + 249 +We're almost done! For completeness' sake, here's my gitea.conf Nginx configuration file as well, so that you have a base to start out with: 250 + 251 +{{code language="nginx"}} 252 + server { 253 + server_name git.pentilescu.com; 254 + 255 + listen [::]:443 ssl http2; # managed by Certbot 256 + listen 443 ssl http2; # managed by Certbot 257 + 258 + 259 + include /etc/nginx/snippets/ssl.conf; 260 + 261 + location / { 262 + proxy_pass http://localhost:3000; 263 + } 264 +} 265 +{{/code}} 266 + 267 +This will redirect all requests meant for "git.pentilescu.com" to localhost port 3000. It also supports TLS, as usual. 268 + 269 +Once you've got everything ready, run the following command to test all your configuration files at once: 270 + 271 +{{code language="bash"}} 272 + sudo nginx -t 273 +{{/code}} 274 + 275 +If Nginx reports that everything is OK, then proceed to restart the service with" 276 + 277 +{{code language="bash"}} 278 + sudo systemctl restart nginx 279 +{{/code}} 280 + 281 +Also, I don't remember if the Nginx daemon is set to run by default on system startup. This is pretty important, as you want all of your web services to be available even in the case of a system reboot. You shouldn't have to manually start Nginx after a system reboot! As such, I recommend running the following to make sure it's enabled: 282 + 283 +{{code language="bash"}} 284 + sudo systemctl enable nginx 285 +{{/code}} 286 + 287 +Also you might have to open firewall ports 80 and 443 to allow Nginx to listen to these. This is specific to your distro so please do that manually. On my end, I don't remember having to do that. I think just installing Nginx did that automatically. Your mileage may vary. 288 + 289 +That's it! Happy coding!