<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nginx on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/nginx/</link><description>Recent content in Nginx on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Mon, 06 Jan 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/nginx/index.xml" rel="self" type="application/rss+xml"/><item><title>Perils of Benchmarking</title><link>https://blog.iankulin.com/perils-of-benchmarking/</link><pubDate>Mon, 06 Jan 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/perils-of-benchmarking/</guid><description>&lt;p&gt;I&amp;rsquo;ve been containerising my websites, with their servers to make deployment simple and robust, and to move to a CI/CD workflow. Since an install of a production web server is large, I would be running about ten of these containers, and there&amp;rsquo;s already a good server facing the net and doing the reverse-proxying (NGINX Proxy Manager), I chose to bundle the Busy-Box httpd server with my sites inside the Docker containers.&lt;/p&gt;
&lt;p&gt;I had a vague feeling that there was a performance vs size compromise involved, and during some googling found this &lt;a href="https://github.com/nerkn/nginx-busybox-apache/tree/main"&gt;github repo&lt;/a&gt; where nerkn has bench-marked busy-box vs apache vs nginx with, to me (because of my choice above), alarming results.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-11-16-at-10.37.19-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If NGINX is doing twice the throughput, and is two orders of magnitude quicker, then busy-box is not going to be a good choice for me.&lt;/p&gt;
&lt;p&gt;Before I panicked, I thought I&amp;rsquo;d do my own A/B tests, which since it&amp;rsquo;s containerised is simple. I used the &lt;a href="https://httpd.apache.org/docs/2.4/programs/ab.html"&gt;apache &lt;code&gt;ab&lt;/code&gt; testing tool&lt;/a&gt; - it spits out the basics - times for connecting, processing, and waiting. It does multiple tests and gives you the mean and standard deviation for them. Perfect.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the results for a series of tests. I included a commercial website I suspect is in the same data centre as a sanity check.&lt;/p&gt;
&lt;table class="has-fixed-layout"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Test&lt;/td&gt;&lt;td&gt;Mean time (ms)&lt;/td&gt;&lt;td&gt;St dev&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nextdc.com.au&lt;/td&gt;&lt;td&gt;834&lt;/td&gt;&lt;td&gt;293&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;busy-box uclibc&lt;/td&gt;&lt;td&gt;450&lt;/td&gt;&lt;td&gt;93&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;busy-box uclibc&lt;/td&gt;&lt;td&gt;411&lt;/td&gt;&lt;td&gt;24&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nginx-alpine&lt;/td&gt;&lt;td&gt;423&lt;/td&gt;&lt;td&gt;24&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nginx-alpine&lt;/td&gt;&lt;td&gt;410&lt;/td&gt;&lt;td&gt;26&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;busy-box uclibc&lt;/td&gt;&lt;td&gt;398&lt;/td&gt;&lt;td&gt;19&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;busy-box uclibc&lt;/td&gt;&lt;td&gt;419&lt;/td&gt;&lt;td&gt;20&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nginx-alpine&lt;/td&gt;&lt;td&gt;403&lt;/td&gt;&lt;td&gt;16&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nginx-alpine&lt;/td&gt;&lt;td&gt;398&lt;/td&gt;&lt;td&gt;23&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nextdc.com.au&lt;/td&gt;&lt;td&gt;759&lt;/td&gt;&lt;td&gt;306&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Huh. A couple of things jump out. One is that the site is probably fast enough, and the other is that the performance of busy-box and NGINX are similar, like very suspiciously similar. I wonder what happens if I &lt;code&gt;docker compose down&lt;/code&gt; the website and run the test again?&amp;hellip;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;This is ApacheBench, Version 2.3 &amp;lt;$Revision: 1903618 $&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Licensed to The Apache Software Foundation, http://www.apache.org/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: 10
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken for tests: 4.608 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: 100
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Non-2xx responses: 100
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: 30300 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: 15400 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 21.70 [#/sec] (mean)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 460.777 [ms] (mean)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 46.078 [ms] (mean, across all concurrent requests)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 6.42 [Kbytes/sec] received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times (ms)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean[+/-sd] median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: 274 318 33.6 306 451
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: 82 95 14.5 92 170
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: 82 95 14.3 92 169
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: 362 413 37.5 401 580
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;lol. Okay. I guess I&amp;rsquo;ve been testing the cache in NGINX Proxy Manager this whole time. There is a setting for that, so perhaps I should turn that off. Sadly, even with that turned off, and the container not running, I&amp;rsquo;m still getting that good performance which would be the 500 error coming back from NGINX Proxy Manager.&lt;/p&gt;
&lt;p&gt;Time to trick it into not using the cache by making unique requests each time. I&amp;rsquo;ll use these:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -n 100 -c 10 &amp;#34;https://example.com.au/index.html?nocache=$(date +%s%N)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -n 100 -c 10 &amp;#34;https://www.nextdc.com/index.html?nocache=$(date%20+%s%N)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m also able to see that it&amp;rsquo;s hitting the container with all the requests by running the compose up in the foreground and having the logs output. So now I&amp;rsquo;m much more confident about the output. Here&amp;rsquo;s the summary of a much larger group of tests run in that round robin style.&lt;/p&gt;
&lt;table class="has-fixed-layout"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Situation&lt;/td&gt;&lt;td&gt;Mean time (ms)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Nextdc.com&lt;/td&gt;&lt;td&gt;617&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;NGINX Proxy with no site&lt;/td&gt;&lt;td&gt;412&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;NGINX-apline site&lt;/td&gt;&lt;td&gt;420&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Busy-box (uclibc)&lt;/td&gt;&lt;td&gt;424&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The comparison with nextdc is of course unfair. They are returning a lot more html, and some of it could be server rendered. I don&amp;rsquo;t have an explanation of why my results are so different from nerkn&amp;rsquo;s. He&amp;rsquo;s using a different tool, and I imagine on a local network (mine is over a mobile data link, to a VPS in a data centre).&lt;/p&gt;
&lt;p&gt;As far as the container size comparisons go, the NGINX-alpine one is 48.98MB and the uclibc version of BusyBox is 1.35MB. I think I&amp;rsquo;ll be sticking with that.&lt;/p&gt;</description></item><item><title>Containerised NGINX Proxy Manager &amp; the 502 error</title><link>https://blog.iankulin.com/containerised-nginx-proxy-manager-the-502-error/</link><pubDate>Mon, 16 Sep 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/containerised-nginx-proxy-manager-the-502-error/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-6.46.49-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-6.46.49-am.png" width="695" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re used to running NGINX Proxy Manager in front of your web apps, and switch to running it in a container, you&amp;rsquo;re going to need to learn a little about Docker networks to get everything connected. If you just do your regular setup, and direct the proxy for an address to &lt;code&gt;127.0.0.1:&amp;lt;some port&amp;gt;&lt;/code&gt;, it won&amp;rsquo;t exist, and you&amp;rsquo;ll visit your page to find the &amp;ldquo;502 Bad Gateway openresty&amp;rdquo; message.&lt;/p&gt;
&lt;p&gt;If you pause to think for a second it will be obvious why - with NGINX Proxy Manager (I&amp;rsquo;m going to start calling it NPM to save myself some typing) &lt;em&gt;inside&lt;/em&gt; a container, any addresses you&amp;rsquo;re entering into the web interface when setting up proxys are &lt;em&gt;inside&lt;/em&gt; the NPM container. &lt;code&gt;127.0.0.1&lt;/code&gt; from that point of view refers to the inside of the NPM container, and not the host, so you exposing a port from your container to the host is not going to work.&lt;/p&gt;
&lt;p&gt;The fix for this is pretty simple, but first let&amp;rsquo;s look at the exception.&lt;/p&gt;
&lt;h3 id="the-exception"&gt;The Exception&lt;/h3&gt;
&lt;p&gt;Usually the very first proxy I add in NPM is to it&amp;rsquo;s own admin interface on port 81. Since this &lt;em&gt;is&lt;/em&gt; inside the NPM container, the setup looks exactly the same as if you were running on the host, and may well be why you were lulled into a false sense of familiarity.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.14.55-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.14.55-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a little bit confusing, since we can also manually access NPM from &lt;code&gt;http://127.0.0.1:81&lt;/code&gt; on the host if we&amp;rsquo;ve exposed port 81 in our compose file, but it&amp;rsquo;s actually a different route. In fact, we could hide port 81 from the host, and the setting above will still work.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my compose file, notice I haven&amp;rsquo;t exposed port 81&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nginx-proxy-manager:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: &amp;#39;jc21/nginx-proxy-manager:latest&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: nginx-proxy-manager
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; restart: unless-stopped
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &amp;#39;80:80&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &amp;#39;443:443&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./data:/data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./letsencrypt:/etc/letsencrypt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So if we try to access port 81 from the host, it won&amp;rsquo;t be answering.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.25.39-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.25.39-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But inside the container, 127.0.0.1 refers to itself, so if we open a shell into the container, the URL http://127.0.0.1:81 refers to something that exists, from there.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.30.49-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.30.49-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="getting-out"&gt;Getting Out&lt;/h3&gt;
&lt;p&gt;So how can our NPM point to a service that&amp;rsquo;s running in another container? You are probably used to specifying ports: in your compose file to expose internal container ports to a host, but that doesn&amp;rsquo;t really help us since NPM in a container can not easily access the host&amp;rsquo;s ports. What we&amp;rsquo;d really like is for NPM to be able to access the second container&amp;rsquo;s ports. And in an ideal world, we&amp;rsquo;d like that to be the only way to access them - that way all the access to our second container service is forced through our proxy. Sounds like security no?&lt;/p&gt;
&lt;p&gt;Turns out this is simple thanks to the magic of Docker networks.&lt;/p&gt;
&lt;p&gt;You can get a fair way with Docker without really thinking or knowing about &lt;a href="https://docs.docker.com/engine/network/"&gt;Docker networks&lt;/a&gt;, and I&amp;rsquo;m really only covering the very basics here - you should probably invest some time in learning about this sometime. Meanwhile, lets run the &lt;code&gt;docker network ls&lt;/code&gt; command and see what networks we&amp;rsquo;ve got.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.41.33-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.41.33-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That network &lt;code&gt;nginx-proxy-manager_default&lt;/code&gt; is the one we&amp;rsquo;re interested in. Its name is just the container name with &amp;ldquo;_default&amp;rdquo; added on the end. What we need to do is just make sure the second container is included in that same network. That&amp;rsquo;s a matter of declaring the external network with that name, and including it in our service definition. I&amp;rsquo;m going to use an NGINX server in my example, but it could be anything. Here&amp;rsquo;s the compose for the second container.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nginx-example.com:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: nginx-example.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./www:/usr/share/nginx/html
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./conf/:/etc/nginx/conf.d/:ro
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; restart: unless-stopped
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - nginx-proxy-manager_default
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;networks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nginx-proxy-manager_default:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; external: true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that I haven&amp;rsquo;t exposed any ports to the host here; We&amp;rsquo;re not going to need them since we&amp;rsquo;ll access this container direct from the NPM container via the internal Docker network docker created for us. That little network even contains a DNS server, so we don&amp;rsquo;t even need to worry about the container&amp;rsquo;s IP addresses, we can just use their names.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-24-at-9.52.54-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So any web requests to &amp;ldquo;example.com&amp;rdquo; arriving at our host go to NPM (since I&amp;rsquo;ve exposed ports 80 &amp;amp; 443 - see the top compose file). Then using the proxy I&amp;rsquo;ve added above, they are forwarded to the container named &amp;ldquo;nginx-example.com&amp;rdquo; which is a DNS record inside the Docker network that Docker created for us, and which both the NPM, and my service, containers are members of.&lt;/p&gt;</description></item><item><title>NGINX Proxy Manager</title><link>https://blog.iankulin.com/nginx-proxy-manager/</link><pubDate>Mon, 15 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/nginx-proxy-manager/</guid><description>&lt;p&gt;I&amp;rsquo;ve mentioned using NGINX as an &lt;a href="https://blog.iankulin.com/nginx-in-front-of-a-node-js-app/"&gt;interface between the internet and a service&lt;/a&gt; a while ago. This works by all incoming traffic coming to NGINX, and NGINX determining which service that traffic should go (from the NGINX config files) then acting as a middleman. This functionality is generally referred to as a &amp;lsquo;reverse proxy&amp;rsquo;.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/nginx.png" width="959" alt="Terrible drawing of NGINX proxying requests off to different services."&gt;
&lt;p&gt;This is nice for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We can have a single point of entry to the services, easier to lock down and secure, with access centrally logged&lt;/li&gt;
&lt;li&gt;The services can be running on all sorts of odd addresses and ports (for example 192.168.101.23:4002) but they can be addressed with sensible names by the user (such as todo.example.com)&lt;/li&gt;
&lt;li&gt;We can add &lt;a href="https://blog.iankulin.com/quick-dirty-auth-with-nginx-node/"&gt;basic auth&lt;/a&gt; to any services that need it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All this stuff is managed through the &lt;a href="https://blog.iankulin.com/nginx-config-on-debian-ubuntu/"&gt;NGINX config&lt;/a&gt; files. Perhaps one might look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;server &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; listen &lt;span style="color:#b48ead"&gt;80&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; server_name example&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;com&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; location &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; root &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;www&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; index index&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;html&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; location &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;app2&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_pass http&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt;&lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;101.65&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;8096&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header Host &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;host&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;Real&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;&lt;span style="color:#bf616a"&gt;IP&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;remote_addr&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;Forwarded&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;For &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;proxy_add_x_forwarded_for&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;Forwarded&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;Proto &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;scheme&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; location &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;secure_area&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; auth_basic &lt;span style="color:#a3be8c"&gt;&amp;#34;Restricted&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; auth_basic_user_file &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;etc&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;nginx&lt;span style="color:#81a1c1"&gt;/.&lt;/span&gt;htpasswd&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;server &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; listen &lt;span style="color:#b48ead"&gt;80&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; server_name app1&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;example&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;com&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; location &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_pass http&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt;&lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;101.23&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;4000&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header Host &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;host&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;Real&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;&lt;span style="color:#bf616a"&gt;IP&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;remote_addr&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;Forwarded&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;For &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;proxy_add_x_forwarded_for&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;Forwarded&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;Proto &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;scheme&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These config files are powerful, and in the usual trade-off somewhat complicated and I&amp;rsquo;ve certainly made problems for myself in the past by making errors in them.&lt;/p&gt;
&lt;p&gt;There is a great project, &lt;a href="https://nginxproxymanager.com/"&gt;NGINX Proxy Manager&lt;/a&gt; that throws a nice web GUI on this process. On top of that, it makes the process of obtaining &lt;a href="https://blog.iankulin.com/certbot-lets-encrypt-are-great/"&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; SSL certificates even easier than CertBot.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/npm.png" width="946" alt="Terrible drawing of NGINX Proxy Manager proxying requests off to different service, and obtaining SSL certificates for them."&gt;
&lt;p&gt;NGINX Proxy Manager is available as a docker image, and is trivial to set up if you&amp;rsquo;re used to docker. Once that&amp;rsquo;s done, the process of adding the proxies is simple enough that you probably don&amp;rsquo;t need any instructions.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-03-31-at-8.59.56-am.png" alt=""&gt;&lt;/p&gt;
&lt;h3 id="alternatives"&gt;Alternatives&lt;/h3&gt;
&lt;p&gt;Rolling your own, or using NGINX Proxy Manager are not your only options. There&amp;rsquo;s also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.haproxy.org/#desc"&gt;HAProxy&lt;/a&gt; - an industrial strength proxy/load balancer&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/quick-starts/reverse-proxy"&gt;Caddy&lt;/a&gt; - same as NGINX but different. Has a great plugin architecture. A particular plugin &lt;a href="https://github.com/lucaslorentz/caddy-docker-proxy"&gt;Caddy-docker-proxy&lt;/a&gt; enables configuration of each service with tables inside the service&amp;rsquo;s docker-compose file which is a particularly neat trick.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://traefik.io/traefik/"&gt;Traefik&lt;/a&gt; - does a similar trick to Caddy-docker-proxy of figuring out it&amp;rsquo;s config from the services it&amp;rsquo;s proxying. It&amp;rsquo;s a serious bit of kit valuable for putting in front of huge Kubernetes swams, and is therefore probably a bit more complex to manage than Caddy-docker-proxy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I haven&amp;rsquo;t used any of these (except NGINX Proxy Manager) so take these descriptions as a starting point only.&lt;/p&gt;
&lt;p&gt;For any self-hosted (at home or on a VPS) services, you are going to need some of this functionality, and NGINX Proxy Manager is a simple, robust approach that should definitely be considered.&lt;/p&gt;</description></item><item><title>Quick &amp;&amp; Dirty auth with nginx &amp; Node</title><link>https://blog.iankulin.com/quick-dirty-auth-with-nginx-node/</link><pubDate>Fri, 23 Feb 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/quick-dirty-auth-with-nginx-node/</guid><description>&lt;p&gt;One of the basic requirements for any serious web app is a proper users/roles/authentication system - but if you&amp;rsquo;re just throwing up a utility of some kind on a public IP for testing, and you don&amp;rsquo;t want it to be abused, then this could be an option. There&amp;rsquo;s a few components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your app. In this demo it&amp;rsquo;s going to be Node, but it could be Go or whatever your server-side poison is. The app is listening for connections on a non-web port (ie not on 80 or 443), I&amp;rsquo;m going to use the traditional 3000.&lt;/li&gt;
&lt;li&gt;A firewall. That port (in my example 3000) must not be accessible from the internet. It has to be blocked by a firewall.&lt;/li&gt;
&lt;li&gt;A web server (I&amp;rsquo;m using nginx) that enforces basic auth.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I briefly discussed web server basic auth earlier - it&amp;rsquo;s a system built into the web server that requires a log in for a route, and authenticates it against the credentials in a password file (usually named &lt;code&gt;.htpasswrd&lt;/code&gt;) and only serves the content if authenticated.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to complicate that a bit by then inserting the authenticated user name into a header, so that we can access it in our node app. The web server does this as it passes the incoming request to our app in a process called proxy-ing.&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;You&amp;rsquo;re going to need a server, separate to the machine you&amp;rsquo;re using. I&amp;rsquo;m going to use an LXC container on one of my Proxmox servers, but perhaps you&amp;rsquo;re on windows and have a WSL to play with, or you&amp;rsquo;ve perhaps you&amp;rsquo;ve spun up a baby server on Hetzner, Linode or Digital Ocean. What ever floats your boat. You need to be able to set it up and &lt;code&gt;ssh&lt;/code&gt; into it to follow along.&lt;/p&gt;
&lt;p&gt;All my examples are assuming Debian, so that or a Debian based distro like Ubuntu is going to be simplest, but if you&amp;rsquo;re on something with a different package management system, you&amp;rsquo;re probably able to translate things to that.&lt;/p&gt;
&lt;h3 id="install-nginx"&gt;Install nginx&lt;/h3&gt;
&lt;p&gt;To install nginx, we just&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo apt install nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now if we open the server ip address, we should see the nginx test page:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-3.23.32-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-3.23.32-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re wondering where this page comes from, it&amp;rsquo;s &lt;code&gt;/var/www/html/index.nginx-debian.html&lt;/code&gt;. There&amp;rsquo;s a default nginx site config at &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; that points to it. We&amp;rsquo;ll be playing in there later.&lt;/p&gt;
&lt;h2 id="installing-node"&gt;Installing Node&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo apt install nodejssudo apt install npm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is going to install the version of node and npm that are provided by Debian or the Debian related distro you&amp;rsquo;re using, so they won&amp;rsquo;t be the latest and greatest, but they will be stable and bug patched to whatever level your distro maintainers think they should be. You could check with &lt;code&gt;node -v&lt;/code&gt; and &lt;code&gt;npm -v&lt;/code&gt; if you were interested, but we&amp;rsquo;re not using any bleeding edge features here, so whatever you&amp;rsquo;ve got it should be fine. For reverence, I have node v18.19.0, and npm 9.2.0&lt;/p&gt;
&lt;h3 id="the-app"&gt;The App&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re going to create a very basic node/Express server app to run on our server. I&amp;rsquo;m going to remote in with VS Code because that&amp;rsquo;s how I roll this week, but do this however you want. Nano is fine, or maybe you&amp;rsquo;re a vim person. Perhaps for these examples we&amp;rsquo;ll assume you&amp;rsquo;re a sane person near the start of their dev journey and use nano. &lt;code&gt;ssh&lt;/code&gt; to the server, then:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir appcd appnpm initnpm install expressnano app.js
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, our app code in app.js&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; express &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; require&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;express&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; app &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; express&lt;span style="color:#eceff4"&gt;();&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; port &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;3000&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;Hello World&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);});&lt;/span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;listen&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;port&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; console&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;log&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;Server is listening at http&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt;localhost&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;port&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we&amp;rsquo;ve done everything right, once you&amp;rsquo;ve saved that (ctl-O, ctl-X) if we run &lt;code&gt;node app.js&lt;/code&gt; we&amp;rsquo;ll get the message &lt;code&gt;Server is listening at http://localhost:3000&lt;/code&gt; and visiting the IP address of our server with &lt;code&gt;:3000&lt;/code&gt; on the end should get this result:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-3.56.39-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-3.56.39-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="the-firewall"&gt;The Firewall&lt;/h3&gt;
&lt;p&gt;Firewalls are their own big thing that I should write about another time. Suffice to say we&amp;rsquo;re going to make it so outside traffic can&amp;rsquo;t access our app on port 3000 (so we can force them to go through nginx where we authenticate them).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo apt&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;get install netfilter&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;persistentsudo iptables &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;A INPUT &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;p tcp &lt;span style="color:#81a1c1"&gt;--&lt;/span&gt;dport &lt;span style="color:#b48ead"&gt;3000&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;j DROPsudo netfilter&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;persistent savesudo netfilter&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;persistent reload
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now if you start the app again with &lt;code&gt;node app.js&lt;/code&gt; and visit :3000 in the browser, it should eventually just time out because the request is never making it to our app.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.17.42-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.17.42-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="proxy-pass"&gt;Proxy Pass&lt;/h3&gt;
&lt;p&gt;So now that raw access from the network to our app is blocked off, we want to configure nginx to pass any requests to our app. There&amp;rsquo;s a number of good reasons why you should put a web server in front of you apps, but today we&amp;rsquo;re doing it so we can authenticate the users. We&amp;rsquo;ll get to that, but for the moment, we need to edit &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Scroll down till you see the &lt;code&gt;location / {&lt;/code&gt; block. Delete out the contents and replace it with&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;proxy_pass http://localhost:3000;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.49.27-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.49.27-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then we&amp;rsquo;ll check the configuration is okay, and restart the nginx server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo nginx -tsudo service nginx restart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now if our app is running (&lt;code&gt;node app.js&lt;/code&gt;) you should be able to go to the server address (without the :3000) and see the app working again.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.55.54-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.55.54-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="credentials"&gt;Credentials&lt;/h3&gt;
&lt;p&gt;Now we need to create a file with our credentials, so nginx can have something to check against. The first web server that I ever used that did this was &lt;a href="https://httpd.apache.org/"&gt;Apache&lt;/a&gt;, and that format has carried forward to be used by nginx. I&amp;rsquo;m mentioning this to explain why I&amp;rsquo;m about to tell you to install some Apache tools.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo apt install apache2-utilssudo htpasswd -c /etc/nginx/.htpasswd user1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This second command is creating (that&amp;rsquo;s the &lt;code&gt;-c&lt;/code&gt; flag) a text file called &lt;code&gt;.htpasswd&lt;/code&gt; in the &lt;code&gt;/etc/nginx&lt;/code&gt; directory. It doesn&amp;rsquo;t matter that much what it&amp;rsquo;s called or where it is - we&amp;rsquo;re going to specify that later in the nginx conf, but I like to put it somewhere I&amp;rsquo;d probably guess later.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;user1&lt;/code&gt; is just what I&amp;rsquo;ve called this user - it could of course be just about anything. htpasswd will ask you to enter a password for this user, and confirm it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-5.55.14-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-5.55.14-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re curious about how that looks in the file, you can just &lt;code&gt;cat&lt;/code&gt; it out. You won&amp;rsquo;t see the plaintext password, it&amp;rsquo;s been hashed into gooblygook.&lt;/p&gt;
&lt;p&gt;If you want to add more users, go ahead; it&amp;rsquo;s the same command without the &lt;code&gt;-c&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo htpasswd /etc/nginx/.htpasswd ian
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we need to tell nginx to use this. We need to go back to the same spot in the &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; where we added the proxy pass statement. Just &lt;em&gt;above&lt;/em&gt; the proxy statement, add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;auth_basic &amp;#34;Protected app&amp;#34;;auth_basic_user_file /etc/nginx/.htpasswd;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;ldquo;Protected app&amp;rdquo; is the explanation that should pop up in the modal, and the other directive just tells nginx where to look for the credentials.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-6.09.17-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-6.09.17-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m pretty sure nginx processes these in order, so put the auth_basic directives before the proxy_pass.&lt;/p&gt;
&lt;p&gt;Once that&amp;rsquo;s saved, we&amp;rsquo;ll check the configuration and restart nginx to load it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ian@ct372-authplay:~$ sudo nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: configuration file /etc/nginx/nginx.conf test is successfulian@ct372-authplay:~$ sudo service nginx restart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we go back to the page, it should pop up and ask for the credentials. If you input your credentials it will direct you to the &amp;ldquo;hello world&amp;rdquo; message from our app.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-6.15.55-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-6.15.55-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="accessing-the-user-in-node"&gt;Accessing the user in node&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s all great, but how do we access the authenticated user in our app so we know what content to serve? Nginx knows the username, but our node app does not. To fix that, nginx needs to put it in the header passed to the app. To do this, we need to edit the nginx conf file again to add:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;proxy_set_header X-Username $remote_user;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This takes the user name (in remote_user) and inserts it to the request header.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-7.39.41-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-7.39.41-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After making this change, we need to restart nginx to pick up the config change again - &lt;code&gt;sudo service nginx restart&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Back in our node app, we need to recover the username from the request header.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; username &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;X-Username&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;Hello &amp;#39;&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;+&lt;/span&gt;username&lt;span style="color:#eceff4"&gt;);});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-7.51.33-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-7.51.33-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the example above I&amp;rsquo;ve extracted the username in the route - often in my apps I do that in middleware and use it to set some request variables with allowed roles and so on.&lt;/p&gt;
&lt;h3 id="limitations"&gt;Limitations&lt;/h3&gt;
&lt;p&gt;This is not a sophisticated system, here are some shortcomings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The most dangerous thing (although I guess this applies to any auth) is that if you&amp;rsquo;re not securing the web traffic with SSL, the password is transmitted in plaintext across the internet.&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s no simple way to logout or change the user.&lt;/li&gt;
&lt;li&gt;I entered wrong credentials about twenty times as fast as I could and it never stopped me trying, so a brute force is possible. There are ways of addressing this that I haven&amp;rsquo;t covered here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All in all, this is a handy tool that doesn&amp;rsquo;t require a lot of libraries or setup. It is very simple and doesn&amp;rsquo;t provide any fancy functionality like password resets, but sometimes it&amp;rsquo;s all you need.&lt;/p&gt;
&lt;h4 id="links"&gt;Links&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/"&gt;NGINX basic auth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Beginning Node App Security</title><link>https://blog.iankulin.com/beginning-node-app-security/</link><pubDate>Fri, 16 Feb 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/beginning-node-app-security/</guid><description>&lt;p&gt;Since I&amp;rsquo;m using Tailscale to painlessly manage all my networking on the homeserver here and my remotes, I&amp;rsquo;ve had the luxury of being a bit casual about the security of my internal apps and self hosted dev tools. I&amp;rsquo;m currently iterating on a web app that requires public access, and is therefore up on a VPS and exposed to all the evils of the open internet.&lt;/p&gt;
&lt;p&gt;I am in no way a security expert, but here&amp;rsquo;s a few of the (reasonably simple) steps I&amp;rsquo;ve taken to secure my node app.&lt;/p&gt;
&lt;h3 id="put-it-behind-nginx"&gt;Put it behind Nginx&lt;/h3&gt;
&lt;p&gt;I could just change the port number of the Node app to listen to port 80 and connect it directly to the world, but Nginx is battle hardened for outward facing tasks so that seems safer. Additionally, it opens up a lot of functionality such as putting my app on a subdomain and some other security options we&amp;rsquo;ll come to. Putting Nginx in front of your app like this is called &amp;lsquo;reverse proxying&amp;rsquo;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	server_name sub.example.com;	location / { 		proxy_pass http://localhost:3000;			proxy_http_version 1.1;		proxy_set_header Upgrade $http_upgrade;		proxy_set_header Connection &amp;#39;upgrade&amp;#39;;		proxy_set_header Host $host;		proxy_cache_bypass $http_upgrade;	}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="basic-auth"&gt;Basic Auth&lt;/h3&gt;
&lt;p&gt;One of the Nginx options is the ability to turn on &amp;lsquo;&lt;a href="https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/"&gt;basic auth&lt;/a&gt;&amp;rsquo;. This can be enabled for a route, subdomain, or a whole domain. It forces a user to authenticate before being able to access resources from that area. It&amp;rsquo;s basic in the sense that the password is manually set on the server in a file that the nginx conf points to. Ideally your app will have comprehensive auth built in, but (especially when you are still developing it) basic auth is a quick and easy way to prevent all of the internet from accessing your app.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	server_name sub.example.com;	location / {		auth_basic &amp;#34;Secure app&amp;#34;;	 auth_basic_user_file /etc/nginx/.htpasswd;	}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="https"&gt;HTTPS&lt;/h3&gt;
&lt;p&gt;Enforcing SSL connections is much easier than it used to be (thank you L&lt;a href="https://blog.iankulin.com/certbot-lets-encrypt-are-great/"&gt;et&amp;rsquo;s Encrypt and Certbot&lt;/a&gt;) and it keeps all the data being sent between your app and the user&amp;rsquo;s browser encrypted - including the username and password you are using for your auth.&lt;/p&gt;
&lt;h3 id="logging"&gt;Logging&lt;/h3&gt;
&lt;p&gt;Ensuring that logs are turned on means that you&amp;rsquo;ve got some chance of detecting problems and possible attacks. In fact, you&amp;rsquo;ll probably be aghast at the number of bots that start accessing your server as soon as it&amp;rsquo;s live. For the most part, they are probing for the existence of known vulnerabilities in well known packages - may of the php. When I look up the IP addresses for these, they almost always come from China or Eastern Europe.&lt;/p&gt;
&lt;p&gt;Note that logging does involve some future maintenance. Logs need rotated and deleted or we&amp;rsquo;ll soon be running out of disk space.&lt;/p&gt;
&lt;h3 id="fail2ban"&gt;Fail2ban&lt;/h3&gt;
&lt;p&gt;Manually checking the logs is not effective, we need to automate this a bit. The things I&amp;rsquo;m looking for in my system is brute force attempts at breaking the basic auth, and the same with SSH.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/fail2ban/fail2ban"&gt;Fail2Ban&lt;/a&gt; can automate this (and many other things, but I&amp;rsquo;m just using these two) by scanning the logs for failed attempts. There are various settings to determine the thresholds - say check for 5 failed attempts in 10 minutes then ban the IP address for 30 minutes. Once the threshold is met, Fail2Ban updates iptables (the internal firewall) to block them.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ian&lt;span style="color:#bf616a"&gt;@&lt;/span&gt;novel&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;ironic&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;~$&lt;/span&gt; sudo fail2ban&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;client status sshdStatus &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; the jail&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; sshd&lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Filter&lt;span style="color:#81a1c1"&gt;|&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Currently failed&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;|&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Total failed&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#b48ead"&gt;2960&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;|&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; &lt;span style="color:#bf616a"&gt;File&lt;/span&gt; list&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;log&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;auth&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;log&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; Actions &lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Currently banned&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#b48ead"&gt;6&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Total banned&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#b48ead"&gt;483&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; Banned &lt;span style="color:#bf616a"&gt;IP&lt;/span&gt; list&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#b48ead"&gt;218.92&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0.27&lt;/span&gt; &lt;span style="color:#b48ead"&gt;61.177&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;172.136&lt;/span&gt; &lt;span style="color:#b48ead"&gt;61.177&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;172.140&lt;/span&gt; &lt;span style="color:#b48ead"&gt;218.92&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0.113&lt;/span&gt; &lt;span style="color:#b48ead"&gt;218.92&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0.31&lt;/span&gt; &lt;span style="color:#b48ead"&gt;218.92&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0.76&lt;/span&gt;ian&lt;span style="color:#bf616a"&gt;@&lt;/span&gt;novel&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;ironic&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;~$&lt;/span&gt; sudo fail2ban&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;client status nginx&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;http&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;authStatus &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; the jail&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; nginx&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;http&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;auth&lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Filter&lt;span style="color:#81a1c1"&gt;|&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Currently failed&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;|&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Total failed&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#b48ead"&gt;6&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;|&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; &lt;span style="color:#bf616a"&gt;File&lt;/span&gt; list&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;log&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;nginx&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;error&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;log&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; Actions &lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Currently banned&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#b48ead"&gt;0&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;|-&lt;/span&gt; Total banned&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	&lt;span style="color:#b48ead"&gt;1&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt; Banned &lt;span style="color:#bf616a"&gt;IP&lt;/span&gt; list&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;	ian&lt;span style="color:#bf616a"&gt;@&lt;/span&gt;novel&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;ironic&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;~$&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the output above, you can see there are 6 ip addresses currently blocked for trying to crack SSH, and there&amp;rsquo;s been 483 banned in the couple of days since I turned it on - so this is a very common attack vector. The basic auth one just has a single ban (from when I tested it). I&amp;rsquo;m not sure why I like looking at the list of bans so much, but I do!&lt;/p&gt;
&lt;h3 id="cloud-firewall"&gt;Cloud Firewall&lt;/h3&gt;
&lt;p&gt;Many VPS providers will have a cloud firewall (although they may call it something else). We can use this to lock down all the ports we are not using to massively reduce the attack surface for this machine. One of the very appealing things about this firewall which is external to the VPS is that it&amp;rsquo;s accessed via the VPS provider web interface - so it&amp;rsquo;s not possible to lock yourself out if you make a mistake - as opposed to when you&amp;rsquo;re SSHd in and fiddling with iptables.&lt;/p&gt;
&lt;p&gt;Since this VPS is just running web apps, I just have ports 80, 443 and 22 open.&lt;/p&gt;
&lt;h3 id="no-root-login"&gt;No root login&lt;/h3&gt;
&lt;p&gt;By default, Ubuntu does not allow root login by password, but once I&amp;rsquo;ve added a new user and added them to the sudo group, I turn it off entirely. Most of those SSH attempts that failed would have been trying common user names including root, so may as well take it out of the possibilities.&lt;/p&gt;
&lt;h3 id="ssh-keys"&gt;SSH keys&lt;/h3&gt;
&lt;p&gt;Apart from being more convenient, well managed SSH keys are much safer than using passwords. So my new user copies up their keys and I set that user to no login with password as well.&lt;/p&gt;
&lt;h3 id="updates"&gt;Updates&lt;/h3&gt;
&lt;p&gt;One of the wonderful things about open source and the modern web, is that as new vulnerabilities are being discovered, they are being patched. We only get them if we run updates though. It&amp;rsquo;s possible (and recommended) to use &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-keep-ubuntu-20-04-servers-updated"&gt;automatic updates&lt;/a&gt;, but I have a weekend ansible routine to do them that I like to look at the output of to check everything&amp;rsquo;s healthy.&lt;/p&gt;
&lt;h3 id="monitoring"&gt;Monitoring&lt;/h3&gt;
&lt;p&gt;I use a &lt;a href="https://blog.iankulin.com/simple-api-endpoint-in-go/"&gt;very simple monitoring system&lt;/a&gt; for all the VM&amp;rsquo;s and containers in my Tailnet - just checking the root disk space and available memory. This is exposed as an http endpoint, then checked by &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;Uptime Kuma&lt;/a&gt;. That&amp;rsquo;s better than nothing, but for a production system not really enough. This is one of the areas I&amp;rsquo;ll revisit in the future.&lt;/p&gt;</description></item><item><title>Certbot - adding more virtual hosts</title><link>https://blog.iankulin.com/certbot-adding-more-virtual-hosts/</link><pubDate>Sun, 15 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/certbot-adding-more-virtual-hosts/</guid><description>&lt;p&gt;I&amp;rsquo;ve got a domain that&amp;rsquo;s not currently used, so I&amp;rsquo;m going to set it up as a virtual host under NGINX. This server is already serving two domains set up with Certbot for SSL. Is it going to be possible to add another site and have Certbot manage the certificates for it after I&amp;rsquo;ve run Certbot once?&lt;/p&gt;
&lt;p&gt;When I googled around to find out, I didn&amp;rsquo;t find anything - which is usually a sign I&amp;rsquo;m either asking a wrong question, or it&amp;rsquo;s so little drama that no one ever mentions it. I decided just to move the site, check it was all working for the http version, then run Certbot and see what it said.&lt;/p&gt;
&lt;p&gt;Since I already had Certbot installed, I just ran &lt;code&gt;sudo certbot --nginx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-09-03-at-10.03.19-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-09-03-at-10.03.19-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s probably worth explaining at this point that Certbot does not obtain separate certificates for each domain (which is what I&amp;rsquo;d been doing when I was doing this manually), but instead grabs a single certificate that includes all the domains, and stores it under the the first domain - in the case above, for agnet.&lt;/p&gt;
&lt;p&gt;I hit &amp;ldquo;E&amp;rdquo; for Expand, and Certbot did it&amp;rsquo;s thing by acquiring the new certificate expanded to cover the new domain and installed it. No drama.&lt;/p&gt;
&lt;h3 id="what-if-you-already-have-a-certificate-from-another-provider"&gt;What if you already have a certificate from another provider?&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve got two more domains to move from another server, but both of these already have active SSL certificates that I obtained via Porkbun. Is that going to be a problem? Can Let&amp;rsquo;s Encrypt (who actually does the certificates for Porkbun) include these sites on the combined certificate on my main VPS so I can use Certbot to maintain them? Let&amp;rsquo;s see.&lt;/p&gt;
&lt;p&gt;I went through the same routine - created a nginx conf for the virtual host in &lt;code&gt;/etc/nginx/sites-available/&lt;/code&gt;, created a simple index.html in &lt;code&gt;/var/www/drysea.xyz&lt;/code&gt; and then symlinked the conf file into &lt;code&gt;/etc/nginx/sites-enabled&lt;/code&gt;. Then changed the A records for the DNS to point to the server address and waited for them to propagate so I could test the http version of the site.&lt;/p&gt;
&lt;p&gt;After that, I ran the sudo certbot &amp;ndash;nginx command again, and exactly as before, it asked if I wanted to expand the existing certificate. I did that, and the site can now be visited securely with no warning about the incorrect certificate. So that&amp;rsquo;s all worked well.&lt;/p&gt;
&lt;p&gt;It is allowable for a site to have more than one active, valid SSL certificate. This often happens in the exact scenario we&amp;rsquo;ve got here where domains are being moved around. There is a security implication for this though. A &lt;a href="https://www.csoonline.com/article/561111/dns-record-will-help-prevent-unauthorized-ssl-certificates.html"&gt;system&lt;/a&gt; of entering a particular DNS record that would prevent certificates being issued by all but one particular certificate authority exists, but is not widely used.&lt;/p&gt;
&lt;p&gt;It is probably a good idea for my to change my configuration on Porkbun to stop it from going on generating certificates that are not needed though, so I&amp;rsquo;ll go ahead and revoke that.&lt;/p&gt;</description></item><item><title>Finding the host IP from inside a Docker container</title><link>https://blog.iankulin.com/finding-the-host-ip-from-inside-a-docker-container/</link><pubDate>Mon, 07 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/finding-the-host-ip-from-inside-a-docker-container/</guid><description>&lt;p&gt;Having successfully set up and tested my node.js api handling app behind nginx on a development VM in the homelab, I decided to move it to my VPS so I could start using it for real. I had a bit of trouble finding the nginx.conf files on the VPS, until I remembered I was running nginx in a docker container on this machine!&lt;/p&gt;
&lt;p&gt;I got everything set up, I could hit the domain in a web browser and get served the static page, and I could &amp;lt;domain_name&amp;gt;:3000/api/gnp_temp.txt and get the file delivered by the node script, but if I tried &amp;lt;domain_name&amp;gt;/api/gnp_temp.txt - &amp;ldquo;Bad Gateway&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I looked at the nginx.conf over and over - it seemed fine. The location block was clearly being triggered, otherwise I&amp;rsquo;d be getting a 404. I dived into stack overflow to no avail. It was if http://localhost:3000 just wasn&amp;rsquo;t working. But it definitely was when I &lt;code&gt;curl&lt;/code&gt;ed it from the command line.&lt;/p&gt;
&lt;p&gt;In desperation I started writing out an explanation to ChatGPT about the setup and my problem, and before I pressed enter realised - from nginx&amp;rsquo;s point of view, http://localhost:3000 was an address &lt;em&gt;inside&lt;/em&gt; the container 🤦, what I needed was the address of the host, from the point of view of the docker container. Surely that must be a common requirement that&amp;rsquo;s been solved.&lt;/p&gt;
&lt;p&gt;From reading around, it seems like I should be able to just substitute &lt;code&gt;host.docker.internal&lt;/code&gt; but that didn&amp;rsquo;t seem to work. I opened a shell into the container to look at &lt;code&gt;ip a&lt;/code&gt; or &lt;code&gt;/sbin/ip route|awk '/default/ { print $3 }'&lt;/code&gt; but of course these containers are slim installs without the general tools you need.&lt;/p&gt;
&lt;p&gt;Docker networking is a whole thing that I should learn, but I haven&amp;rsquo;t yet, I just start up containers with whatever the defaults for networking are. But I figured the host must be part of the docker network, and a quick look in &lt;code&gt;ip a | grep docker&lt;/code&gt; produced this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3: docker0: &amp;lt;NO-CARRIER,BROADCAST,MULTICAST,UP&amp;gt; mtu 1500 qdisc noqueue state DOWN group default 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bingo. I popped that IP address into my nginx.conf and everything started working perfectly. That was forty minutes of learning I wouldn&amp;rsquo;t have had to live through if I could have just turned around to a work colleague and asked.&lt;/p&gt;</description></item><item><title>nginx in Front of a node.js app</title><link>https://blog.iankulin.com/nginx-in-front-of-a-node-js-app/</link><pubDate>Fri, 04 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/nginx-in-front-of-a-node-js-app/</guid><description>&lt;p&gt;NGINX is a great webserver and reverse proxy - as in it can hand off requests to other web-servers. That&amp;rsquo;s the situation I want to have set up on my VPS. I want NGINX to handle incoming requests - some of them will just be sorted out by returning static HTML, others (like the weather api I&amp;rsquo;ve been playing with) need to be handed off to other services to respond to.&lt;/p&gt;
&lt;p&gt;In the situation I&amp;rsquo;m looking at, I want requests that have the route /api (eg example.com/api/weather) to be passed to a node.js program I&amp;rsquo;ve written. All the other http requests should just be treated as requests for static pages and dealt with by NGINX.&lt;/p&gt;
&lt;p&gt;So I guess is part V of my adventures in the weather API, if you just want to know how to set up NGINX to serve static pages AND pass some routes off to node, you don&amp;rsquo;t need to be up to date on these.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/outside-temperature-from-an-api-in-a-shell-script/"&gt;Outside Temperature From an API in a Shell Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/complicating-the-temperature-api/"&gt;Complicating the Temperature API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/how-to-deploy-a-node-js-app/"&gt;Using Node.js to serve a static file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/how-to-deploy-a-node-js-app/"&gt;How to Deploy a Node.js App&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="nginx-configuration"&gt;nginx Configuration&lt;/h3&gt;
&lt;p&gt;Once nginx and node.js are installed (with &lt;a href="https://gist.github.com/IanKulin/bd6d1a78f9a9fa9a859384a26ca95235"&gt;the Ansible script&lt;/a&gt; if you want to rock the dev ops tattoo) you&amp;rsquo;ll need to configure nginx. On my Debian systems, the config file &lt;code&gt;nginx.conf&lt;/code&gt; is in &lt;code&gt;/etc/nginx/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a line in that file that includes all the &lt;code&gt;*.conf&lt;/code&gt; files in &lt;code&gt;/etc/nginx/conf.d&lt;/code&gt;. This is a common pattern I see in some distros - the main config files are not really meant to be messed with, but then there&amp;rsquo;s a directory to add config files to whihc are included. The theoretical advantage of this is that the distro maintainers can roll out a new version of a package and change the main config file, and your stuff will still work.&lt;/p&gt;
&lt;p&gt;The way they have done this with the nginx.conf means that the only changes we can make in the &lt;code&gt;conf.d&lt;/code&gt; directory are to do with virtual hosts, but that&amp;rsquo;s going to be 99% of the things we would want to change. So much so, I&amp;rsquo;m not even going to show you the &lt;code&gt;nginx.conf&lt;/code&gt; file, just our little &lt;code&gt;/etc/nginx/conf.d/nodeapi.conf&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; server &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; listen &lt;span style="color:#b48ead"&gt;80&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; server_name &lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100.40&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;# Serve static files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; root &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;www&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;# pass api requests to node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; location &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;api &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_pass http&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt;localhost&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3000&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header Host &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;host&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Okay, we are listening on port 80, and this &amp;ldquo;server block&amp;rdquo; is only for requests like http://192.168.100.40. The purpose of &lt;code&gt;server_name&lt;/code&gt; is that we might run the websites for several domains from one nginx installation. For example, we might be serving example.com and otherexample.com from the same VPS.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;root /var/www;&lt;/code&gt; - tells nginx to grab the files from that directory, so that&amp;rsquo;s the static web server part.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;location /api {&lt;/code&gt; - is telling nginx &amp;ldquo;all the requests with /api on the end are dealt with differently, look in this block for instructions&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;proxy_pass http://localhost:3000;&lt;/code&gt; - send them all to a server on this machine listening on port 3000. This is where the node/express server is running.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;proxy_set_header Host $host;&lt;/code&gt; - it doesn&amp;rsquo;t matter for the purposes of this api, but it&amp;rsquo;s often nice to tell the server being proxied who the real host receiving the request is. If we didn&amp;rsquo;t do this, the node app would only be able to see that &amp;ldquo;localhost&amp;rdquo; was making a request. By doing this, it knows the request was to the server running nginx, in this case 192.168.100.40, but usually a real domain name. This might be needed if our node app was also servicing more than one domain.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it. We&amp;rsquo;re done. You do need to have your node app running on port 3000 on the same machine, but as long as that&amp;rsquo;s happening this should all be working. Do remember to restart nginx each time you make a config changes with &lt;code&gt;sudo service nginx restart&lt;/code&gt;, and it would also be good practice to check the config files with &lt;code&gt;sudo nginx&lt;/code&gt; -t before that.&lt;/p&gt;</description></item><item><title>Updating SSL Certificates</title><link>https://blog.iankulin.com/updating-ssl-certificates/</link><pubDate>Wed, 12 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/updating-ssl-certificates/</guid><description>&lt;p&gt;When I first installed my SSL certificates, &lt;a href="https://blog.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/"&gt;I mentioned&lt;/a&gt; it&amp;rsquo;s a process I need to automate before they came up for expiry, but here we are ten days out, and I haven&amp;rsquo;t done that yet, but I have been keeping an eye on it though the excellent display and notifications set up in &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;Uptime Kuma&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-5.36.01-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-5.36.01-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Updating the certificates is easy. When I went into the site at PorkBun (where I purchased the domain and who do the primary DNS for the site, the next certificates were sitting there to be downloaded. My existing certificates were due to expire on 30th July, and these had been generated on 3rd July.&lt;/p&gt;
&lt;p&gt;The bundle included the same files as last time. You might remember from last &lt;a href="https://blog.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/"&gt;time&lt;/a&gt; that we need to join the &lt;code&gt;domain.cert.pem&lt;/code&gt; and &lt;code&gt;intermediate.cert.pem&lt;/code&gt; to make the &lt;code&gt;fullchain.pem&lt;/code&gt; file. I had just &lt;code&gt;cat&lt;/code&gt;&amp;rsquo;d them together and this had caused an issue as there&amp;rsquo;s no newline character at the end of the first file. I got smarter this time and googled up this &lt;a href="https://stackoverflow.com/questions/8183191/concatenating-files-and-insert-new-line-in-between-files/23549826#23549826"&gt;solution&lt;/a&gt; which did the trick by using echo to insert the newline:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-5.57.44-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-5.57.44-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once that was done, I uploaded them to the nginx directory where I stored them last time. Nginx reloads the config on restart, although there&amp;rsquo;s probably a neater way as well, so I just restarted the container with Docker compose to pick up the new certificates. While I was doing that I got the ping from Uptime Kuma via &lt;a href="https://ntfy.sh/"&gt;ntfy&lt;/a&gt; to say it was down, then up. I had a look at the display, and it&amp;rsquo;s showing I&amp;rsquo;ve got another 84 days left on the cert.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-6.10.32-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-6.10.32-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, 84 days for me to get around to automating this.&lt;/p&gt;</description></item><item><title>Using Node.js to return a static file</title><link>https://blog.iankulin.com/using-node-js-to-return-a-static-file/</link><pubDate>Sun, 02 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/using-node-js-to-return-a-static-file/</guid><description>&lt;p&gt;As mentioned in the &lt;a href="https://blog.iankulin.com/complicating-the-temperature-api/"&gt;previous post&lt;/a&gt;, stage one is just to return the same static text file, but from the Node server, rather than NGINX. That&amp;rsquo;s non-trivial to a rank beginner since I need to figure out 1) how to serve a static file from Node, and 2) how to configure NGINX to hand off calls to the API to Node. This post will look at both of those, but it&amp;rsquo;s first probably worth just setting out what each of the puzzle pieces are.&lt;/p&gt;
&lt;h3 id="nginx"&gt;NGINX&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.nginx.com/"&gt;NGINX&lt;/a&gt; is a web server - it listens on a port (classically 80 and 443 - http and https) and responds to those requests. Usually by returning some files. However, it can also pass those requests off to something else. This process is called Reverse Proxying. Currently I have NGINX set up to just serve a static text file, but in the change I&amp;rsquo;m proposing, NGINX will pass an API request off to Node.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/JKxlsvZXG7c?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;h3 id="nodejs"&gt;Node.js&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://nodejs.org/en"&gt;Node&lt;/a&gt; is JavaScript packaged up to run on a server, instead of inside a browser. There&amp;rsquo;s lots of different languages we can write server-side code in, and many have some strengths over Javascript. Part of the motivation for using Node might be that web developers have already invested significantly in learning JavaScript to use on the front-end, so it makes sense to use those same skills on the back end.&lt;/p&gt;
&lt;p&gt;A major difference from some other server-side scripting languages (for example, PHP) is that Node is non-blocking, making use of call-backs to handle events resulting in high performance at scale. It&amp;rsquo;s trivial to write a static web server in Node, but that is to seriously under-use it&amp;rsquo;s capability.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/jOupHNvDIq8?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;h3 id="expressjs"&gt;Express.js&lt;/h3&gt;
&lt;p&gt;Once you start writing backends in Node, you&amp;rsquo;ll find yourself writing a lot of the same code over and over to achieve some standard things - time for a framework. &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt; is one of the most popular web frameworks for writing APIs on Node. Using Express makes that job simpler and leaves you with cleaner, more succinct code. It&amp;rsquo;s can be argued that there are better frameworks, but at around 5 miliion downloads per day, I think we can regard it as a standard approach to the problems it solves.&lt;/p&gt;
&lt;h3 id="serve-a-static-file-from-node"&gt;Serve a static file from Node&lt;/h3&gt;
&lt;p&gt;I said it was trivial. Here&amp;rsquo;s the code, then we&amp;rsquo;ll discuss it:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-06-25-at-8.45.20-am.png" alt="const express = require(&amp;rsquo;express&amp;rsquo;);
const app = express();
const PORT = 3000;
app.get(&amp;quot;/api/gnp_temp.txt&amp;quot;, (req, res) =&amp;gt; {
res.status(200).sendFile(__dirname + &amp;lsquo;/gnp_temp.txt&amp;rsquo;);
});
app.listen(PORT, () =&amp;gt; {console.log(`Listening on port ${PORT}`)});"&gt;&lt;/p&gt;
&lt;p&gt;PORT is the port we&amp;rsquo;re listening on. In this case 3000. So if I open a URL on http://localhost:3000 that request will be handled by this code. The actual work is done in these lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.get(&amp;#34;/api/gnp_temp.txt&amp;#34;, (req, res) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res.status(200).sendFile(__dirname + &amp;#39;/gnp_temp.txt&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It is only looking for requests to &lt;code&gt;:3000/api/gnp_temp.txt&lt;/code&gt; - everything else is ignored. But if it gets that request, it will return a result status of &lt;code&gt;200&lt;/code&gt; (success) along with the file &lt;code&gt;gnp_temp.txt&lt;/code&gt; from the current directory.&lt;/p&gt;
&lt;p&gt;If you are wondering about setting up the environment to get to the point where you can run and understand this. There are lots of great videos - &lt;a href="https://www.youtube.com/watch?v=SccSCuHhOw0"&gt;Web Dev Simplified&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=pKd0Rpw7O48"&gt;Code with Mosh&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=KNa-wMpry00"&gt;Code with Con&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now that it Works on My Machine™ I need to figure out how to deploy it.&lt;/p&gt;</description></item><item><title>Installing SSL Certificates with Nginx on Docker</title><link>https://blog.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/</link><pubDate>Sat, 29 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/</guid><description>&lt;p&gt;When you&amp;rsquo;ve successfully got Nginx running in a Docker container, AND got your &lt;a href="https://blog.iankulin.com/adding-a-domain-name-to-a-vps/"&gt;domain correctly pointing&lt;/a&gt; at your nascent website, you&amp;rsquo;re then going to want to set it up for encrypted, and therefore trusted, browsing with SSL.&lt;/p&gt;
&lt;h3 id="certificates"&gt;Certificates&lt;/h3&gt;
&lt;p&gt;A couple of posts ago, I &lt;a href="https://blog.iankulin.com/adding-a-domain-name-to-a-vps/"&gt;mentioned&lt;/a&gt; that it was simpler to let Porkbun be the authoritative nameserver for a domain. Part of the reason for that is that if we do that, Porkbun had a button you can press which connects to LetsEncrypt and generates the certificates for you. This usually takes an hour or so, then you&amp;rsquo;ll be able to download the bundle from that same page.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-21-at-2.30.58-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-21-at-2.30.58-pm.png" width="913" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In order for the SSL to work, we&amp;rsquo;re going to have to make a couple of files available to Nginx - &lt;code&gt;fullchain.pem&lt;/code&gt; and &lt;code&gt;private.key.pem&lt;/code&gt;. So there&amp;rsquo;s our first gotcha - we don&amp;rsquo;t have a &lt;code&gt;fullchain.pem&lt;/code&gt;, so we have to build it. To do this, we just combine the domain certificate and the intermediate certificate. On the mac, I did this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat domain.cert.pem intermediate.cert.pem &amp;gt; fullchain.pem
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is me solving the first gotcha, while simultaneously creating the second. Much later in the process when Nginx was failing at startup, I looked in the logs (with the handy &lt;code&gt;docker logs&lt;/code&gt; command) and saw these messages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;2023&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;04&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;21&lt;/span&gt; &lt;span style="color:#b48ead"&gt;05&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;42&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;45&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;emerg&lt;span style="color:#eceff4"&gt;]&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#616e87;font-style:italic"&gt;#1: cannot load certificate &amp;#34;/etc/nginx/conf.d/fullchain.pem&amp;#34;: PEM_read_bio_X509() failed (SSL: error:0908F066:PEM routines:get_header_and_data:bad end line)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nginx&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;emerg&lt;span style="color:#eceff4"&gt;]&lt;/span&gt; cannot &lt;span style="color:#81a1c1"&gt;load&lt;/span&gt; certificate &lt;span style="color:#a3be8c"&gt;&amp;#34;/etc/nginx/conf.d/fullchain.pem&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; PEM_read_bio_X509&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; failed &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;SSL&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; error&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0908&lt;/span&gt;F066&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;PEM routines&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;get_header_and_data&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;bad end line&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s a reasonably descriptive error - let&amp;rsquo;s look in the &lt;code&gt;fullchain.pem&lt;/code&gt; file (it&amp;rsquo;s just text like an SSH key file) and see if there&amp;rsquo;s anything suspicious.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-21-at-1.46.32-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Well there&amp;rsquo;s a problem. These beginning and ends should be on their own lines - I probably could have done that when I concatenated them, but no problem, it&amp;rsquo;s easily fixed in the text editor by counting in five dashes and hitting enter.&lt;/p&gt;
&lt;h3 id="nginx-docker"&gt;Nginx Docker&lt;/h3&gt;
&lt;p&gt;In order to have the certificates work with Nginx, we&amp;rsquo;re going to need to add them to the a config file. There&amp;rsquo;s also a couple of gotcha&amp;rsquo;s in that process.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re as new to running Nginx in a container as I am, you might have been starting it up with a command like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 80:80 -d -v ~/www:/usr/share/nginx/html nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s fine and all, but as your system gets a bit more complex (which it&amp;rsquo;s about to) this will quickly become unmanageable. It&amp;rsquo;s time to put your big person pants on and embrace the wonders of &lt;code&gt;docker compose&lt;/code&gt;. There are many resources for learning this, but the short version is that all of that information you&amp;rsquo;ve got in your command line can be stored in a human readable YAML file. If you&amp;rsquo;re smart it will also be in version control and you&amp;rsquo;re on your journey to automating your infrastructure as code.&lt;/p&gt;
&lt;p&gt;Below is my &lt;code&gt;docker-compose.yaml&lt;/code&gt; file for Nginx. Note that these files are always called that, so you keep the compose files for different containers in separate directories.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;version: &amp;#34;3.9&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; client:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 80:80
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 443:443
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /home/ian/iankulin.com/www:/usr/share/nginx/html
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /home/ian/iankulin.com/nginx/conf/:/etc/nginx/conf.d/:ro
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; restart: always
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;version&lt;/code&gt; - just the compose yaml version docker should use to read this file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;services/client&lt;/code&gt; - it&amp;rsquo;s possible to combine several programs (clients) in a docker container. We&amp;rsquo;re not doing that today&lt;/li&gt;
&lt;li&gt;&lt;code&gt;image&lt;/code&gt; - the name of the docker image we&amp;rsquo;re pulling down from &lt;a href="https://hub.docker.com/"&gt;docker hub&lt;/a&gt;. We could also add the version here if we were picky, if one&amp;rsquo;s not specified, it assumes &amp;rsquo;latest'&lt;/li&gt;
&lt;li&gt;&lt;code&gt;container_name&lt;/code&gt; - nice name for our container - it&amp;rsquo;s possible to run several versions of the same image so you may want to name them something different. If you miss this off, docker will make up a default name like &lt;code&gt;agressive_einstein&lt;/code&gt; and you&amp;rsquo;ll constantly be running &lt;code&gt;docker ps&lt;/code&gt; because you can&amp;rsquo;t remember the name&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ports&lt;/code&gt; - the underlying idea of containers is that they are mostly immutable inside, but of course to be useful they need to have some access to the outside world. This ports declaration is doing just that. These two lines are saying port 80 outside the container is connected to port 80 inside the container. If we wanted Nginx running on port 8080 we&amp;rsquo;d say &lt;code&gt;8080:80&lt;/code&gt; ie the outside first, and the inside second&lt;/li&gt;
&lt;li&gt;&lt;code&gt;volumes&lt;/code&gt; - similar to ports, we&amp;rsquo;re joining a directory of our file system in outside world to a directory inside the container. In the first one, Nginx is going to look for files to serve inside the container at &lt;code&gt;/usr/share/nginx/html&lt;/code&gt; but where we want it to look for html files to serve is actually out here in the real filesystem world. Same as with the ports, the format is real world first, inside the container second. Same with the config directory. You might be wondering how I know what the paths inside the container are for these things - you just have to figure it out from the &lt;a href="https://hub.docker.com/_/nginx"&gt;documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;So we&amp;rsquo;ve got two outside world locations - our html files in &lt;code&gt;/home/ian/iankulin.com/www&lt;/code&gt; and the Nginx config files in &lt;code&gt;/home/ian/iankulin.com/nginx/conf/&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at the config file. This is stored in &lt;code&gt;/home/ian/iankulin.com/nginx/conf/&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;server {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; listen 80 default_server;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; listen [::]:80 default_server;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; root /usr/share/nginx/html;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; server_name iankulin.com www.iankulin.com;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; listen 443 ssl; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; # RSA certificate
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_certificate /etc/nginx/conf.d/fullchain.pem; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_certificate_key /etc/nginx/conf.d/private.key.pem; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is mostly pretty decodable just by looking at it, but there&amp;rsquo;s a couple of things worth noting.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the root for the html, like all of the paths in this file, are the paths &lt;em&gt;inside&lt;/em&gt; the container. Don&amp;rsquo;t get confused. To the programs running inside the container, everything looks like it&amp;rsquo;s inside the container. This config file is being consumed by the Nginx program inside the container, so the paths have to be inside-the-container paths.&lt;/li&gt;
&lt;li&gt;Following that logic, I&amp;rsquo;ve actually stored the SSL certificates at &lt;code&gt;/home/ian/iankulin.com/nginx/conf/&lt;/code&gt; but to Nginx inside the container, they look like they&amp;rsquo;re at &lt;code&gt;/etc/nginx/conf.d/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;There is &lt;em&gt;way&lt;/em&gt; more stuff you can do in this config file. This is just the simplest version possible to make things work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So now that&amp;rsquo;s in place, and I&amp;rsquo;ve got a skeleton of an index.html file stored at &lt;code&gt;/home/ian/iankulin.com/www&lt;/code&gt; I just enter &lt;code&gt;sudo docker compose up -d&lt;/code&gt; in the directory where my &lt;code&gt;docker-compose.yaml&lt;/code&gt; file is, and I should be able to navigate to &lt;code&gt;http**s**://iankulin.com&lt;/code&gt; and get a webpage with a padlock in the corner.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-04-21-at-1.48.00-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-21-at-1.48.00-pm.png" width="859" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="success"&gt;Success&lt;/h3&gt;
&lt;p&gt;Well of sorts. We have obtained our certificates, and installed them in the webserver, but certificates like these only last 90 days. In 75 days I can obtain new certificates and copy them over the old ones. If we fail to do that by the 90th day, visitors to the website will get a scary message saying the website might not be who it says it is, and users will have to click around a bit to ignore it. You will have almost certainly seen this message as it&amp;rsquo;s a reasonably common problem.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a problem calling out for an automated solution, of which there &lt;a href="https://certbot.eff.org/"&gt;is one&lt;/a&gt; that we&amp;rsquo;ll install on another day. Probably the day I come back to this server and discover the certificates have expired&amp;hellip;&lt;/p&gt;</description></item></channel></rss>