<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ssl on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/ssl/</link><description>Recent content in Ssl on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Mon, 31 Mar 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/ssl/index.xml" rel="self" type="application/rss+xml"/><item><title>Manually adding SSL certs in Nginx Proxy Manager</title><link>https://blog.iankulin.com/manually-adding-ssl-certs-in-nginx-proxy-manager/</link><pubDate>Mon, 31 Mar 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/manually-adding-ssl-certs-in-nginx-proxy-manager/</guid><description>&lt;p&gt;A large part of the reason for my use of Nginx Proxy manager over vanilla NGINX, is that it has built-in Let&amp;rsquo;s Encrypt certificate requesting and renewing. This works perfectly for all my public facing services, and until recently, my homelab services. Before I dive into how I&amp;rsquo;ve fixed the problem I ran into, I better explain how my homelab domain is set up, and before I do that, an over-simplified description of how the SSL system works is required&lt;/p&gt;
&lt;h3 id="ssl"&gt;SSL&lt;/h3&gt;
&lt;p&gt;SSL (Secure Socket Layer) on a web site (the little padlock you see in the browser when you visit an https:// site) does three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It tells you that this web site you&amp;rsquo;ve visited is controlled by the same people who own the domain name. This is important to prevent someone hijacking your request to &amp;ldquo;mysecurebank.com&amp;rdquo; and sending it to their password stealing website.&lt;/li&gt;
&lt;li&gt;It encrypts the traffic between the web-browser and the website so it can&amp;rsquo;t be spied on.&lt;/li&gt;
&lt;li&gt;It detects is someone has tried to tamper with the traffic between the web-browser and the website.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For all this to work, there needs to be some way of assuring the certificate issuer that the entity claiming a certificate for the domain, has control of the website that the domain is pointing to. The simplest way to do this is for the certificate issuer to give you a token, you install that in a secret directory on the web server, then the certificate issuer can check it exists. This proves to them that you own the website, and they can issue you the certificate.&lt;/p&gt;
&lt;p&gt;This process is essentially what happens when you use &lt;a href="https://certbot.eff.org/"&gt;Certbot&lt;/a&gt; to obtain a &lt;a href="https://letsencrypt.org/"&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; SSL certificate. It works great as long as your website is public to the internet so it can be contacted by the certificate issuer. But, that&amp;rsquo;s not the case for my homelab services. They are on an internal network, deliberately not contactable from the wild internet.&lt;/p&gt;
&lt;h3 id="ssl-on-an-internal-network"&gt;SSL on an internal network&lt;/h3&gt;
&lt;p&gt;There is less need for SSL on an internal network. You probably assume there&amp;rsquo;s no one to spy on your traffic, or to fiddle with it, and you know you have control of your apps. Apart from not trusting that, there&amp;rsquo;s a couple of other benefits - you often can&amp;rsquo;t save your passwords for unsecured sites, you can get annoying warning messages, and some apps just straight up won&amp;rsquo;t let you use some functionality without it. This is the case for my Forgejo instance that wants working SSL to allow me to git push to it.&lt;/p&gt;
&lt;h3 id="homelab-ssl"&gt;Homelab SSL&lt;/h3&gt;
&lt;p&gt;There is a way around this - basically we need to assure the certificate issuer that we&amp;rsquo;re in control, but instead of exposing a token on the (unreachable) web server, we add it as a record in the DNS of the domain (called DNS Challenge). The logic of this is that if you control the DNS you control where it points and therefore the web server. This is the first part of the Homelab SSL setup - obtaining the certificate with DNS challenge. The second part is using the DNS to point the domain at an internal website address. My domain and DNS are public - you could enter the domain name in a browser, but it resolves to an address inside my network. This is all much better explained by Wolfgang than me.&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/qlcVx-k-02E?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="the-problem-ive-run-into"&gt;The problem I&amp;rsquo;ve run into&lt;/h3&gt;
&lt;p&gt;This all worked perfectly when I first set it up. Nginx Proxy Manager (NPM) has a plugin for my domain provider (&lt;a href="http://porkbun.com"&gt;porkbun&lt;/a&gt;) which uses their API to save the token into my DNS settings. The UX for this is that I tell Nginx Proxy Manager that I want to use &amp;ldquo;DNS Challenge&amp;rdquo; for a certificate request, and (by using an API key I&amp;rsquo;ve setup on Porkbun and given to NPM) it does all the fiddling around to obtain the certificate then installs them.&lt;/p&gt;
&lt;p&gt;I must of had that running for a year or so, with the certificates magically being renewed every couple of months with no input from my until just recently. I&amp;rsquo;m not exactly sure what&amp;rsquo;s happened - the error messages that I&amp;rsquo;m not smart enough to sort out suggest that the plugin&amp;rsquo;s operations to install the token at the domain provider is not working. I don&amp;rsquo;t know if it&amp;rsquo;s an API problem, or there&amp;rsquo;s been an NPM update that&amp;rsquo;s broken the plugin, or just something else has changed in my setup. What ever it is, turning everything off and on again, updating everything, and trying manually have not worked. So time for plan B.&lt;/p&gt;
&lt;h3 id="manual-certificates"&gt;Manual Certificates&lt;/h3&gt;
&lt;p&gt;Porkbun (and for all I know other domain sellers) provide a facility to download a &amp;lsquo;certificate bundle&amp;rsquo; directly from them - they are just doing that Let&amp;rsquo;s Encrypt dance directly - missing the NPM and Porkbun API step from the above.&lt;/p&gt;
&lt;p&gt;The certificate bundle contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public.key.pem&lt;/li&gt;
&lt;li&gt;private.key.pem&lt;/li&gt;
&lt;li&gt;domain.cert.pem&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And when you go to add a custom certificate in NPM you have these options:&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-03-08-at-15.47.04.png" width="981" alt=""&gt;
&lt;p&gt;As you can see your certificate (domain.cert.pem) goes in the certificate slot, and the Certificate Key it&amp;rsquo;s asking for is your private key (private.key.pem). You don&amp;rsquo;t need the intermediate key - this is the same for all Let&amp;rsquo;s Encrypt certificates.&lt;/p&gt;
&lt;p&gt;Doing the certificates this way is less good than having them automatically renewed. Currently Let&amp;rsquo;s Encrypt certificates are good for 90 days, so every three months my monitoring system will let me know they only have a couple of weeks left and I&amp;rsquo;ll have to repeat this manual process. There has been talk of shortening this time which would make that process even more annoying, so hopefully I can sort out the issue in NPM, or find out if Traefik or Caddy have the necessary plugins to do DNS challenge certificates.&lt;/p&gt;
&lt;p&gt;But in the meantime, my internal web apps are all up and secure.&lt;/p&gt;
&lt;h3 id="this-is-not-free"&gt;This is not free&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s Encrypt, and to a lesser extent Certbot have changed the web substantially. Obtaining and installing certificates used to be a difficult and costly process, but these two &amp;lsquo;free&amp;rsquo; services have turned that around. If you run a website and use these services I highly recommend you support the non-profits that keep them in existence as I do.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let&amp;rsquo;s Encrypt - &lt;a href="https://letsencrypt.org/donate/"&gt;have a donation page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Certbot - is provided by the EFF who do other work can be &lt;a href="https://supporters.eff.org/donate/support-work-on-certbot"&gt;donated to here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Fixing TLS for wget in BusyBox</title><link>https://blog.iankulin.com/fixing-tls-for-wget-in-busybox/</link><pubDate>Mon, 25 Nov 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/fixing-tls-for-wget-in-busybox/</guid><description>&lt;p&gt;I&amp;rsquo;ve been containerising my static websites with BusyBox (because it&amp;rsquo;s small), and in &lt;a href="https://blog.iankulin.com/fancier-website-in-a-docker-container/"&gt;an earlier post&lt;/a&gt; showed how to even get the container to update parts of the site by reaching out with &lt;code&gt;wget&lt;/code&gt; to download resources from elsewhere and saving them inside the container where we are serving the &amp;lsquo;static&amp;rsquo; site from. I&amp;rsquo;d done this by including a bash script in the container with the &lt;code&gt;wget&lt;/code&gt; in a loop with a &lt;code&gt;sleep&lt;/code&gt;. Then started the script and the httpd server in the CMD line of the &lt;code&gt;dockerfile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the dockerfile.&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;FROM busybox&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;latest
&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;# Add shell script and set executable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;COPY update_content&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;sh &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;usr&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;local&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;bin&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;update_content&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;sh
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RUN chmod &lt;span style="color:#81a1c1"&gt;+&lt;/span&gt;x &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;usr&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;local&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;bin&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;update_content&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;sh
&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;# Create the directory for the web content, and copy files in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RUN mkdir &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;p &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:#81a1c1"&gt;/&lt;/span&gt;html
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;COPY www&lt;span style="color:#81a1c1"&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;www&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;html
&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;# Expose port 80 for the web server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;EXPOSE &lt;span style="color:#b48ead"&gt;80&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;# Start the httpd server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CMD &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;sh&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;/usr/local/bin/update_content.sh &amp;amp; busybox httpd -f -p 80 -h /var/www/html&amp;#34;&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;And the bash script:&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#5e81ac;font-style:italic"&gt;#!/bin/sh
&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;# Define the URL and the destination path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;URL&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;http://httpbin.org/image/jpeg&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DEST_PATH&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;/var/www/html/image.jpg&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FETCH_INTERVAL&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;&lt;span style="color:#b48ead"&gt;120&lt;/span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;# 2 minutes&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:#81a1c1;font-weight:bold"&gt;while&lt;/span&gt; true&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;do&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;# Use wget to download the file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wget -O &lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;$DEST_PATH&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;$URL&lt;span style="color:#a3be8c"&gt;&amp;#34;&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;# Check the exit status of wget&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;[&lt;/span&gt; $? -eq &lt;span style="color:#b48ead"&gt;0&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;]&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;echo&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;File downloaded successfully to &lt;/span&gt;$DEST_PATH&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;echo&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Failed to download the file.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sleep $FETCH_INTERVAL
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This all works perfectly, as long as the file you&amp;rsquo;re downloading is over http. Trying over an SSL connection (which must be 98% of the internet by now) fails. The reason for this is that &lt;a href="https://blog.iankulin.com/fancier-website-in-a-docker-container/"&gt;BusyBox does not contain the certificates for the root CA&lt;/a&gt;. In a normal distribution, you&amp;rsquo;d just do ahead and install them, but BusyBox also does not have a package manager to help you do that, so there&amp;rsquo;s no &amp;lsquo;&lt;code&gt;apk update &amp;amp;&amp;amp; apk add ca-certificates&lt;/code&gt;&amp;rsquo; to help us out.&lt;/p&gt;
&lt;p&gt;A viable solution might be to just switch to an Alpine container, but I&amp;rsquo;d be going up to 12MB per containerised website then (from 4) which seems a bit much.&lt;/p&gt;
&lt;p&gt;In a &lt;a href="https://blog.iankulin.com/fancier-website-in-a-docker-container/"&gt;Stack Overflow post&lt;/a&gt;, &lt;a href="https://stackoverflow.com/users/2830850/tarun-lalwani"&gt;Tarun Lalwani&lt;/a&gt; offers a couple of suggestions. One is having a multi-stage docker image build where you create an Alpine container, copy the certs out to a volume, then copy them into your busybox image. To my mind that would be a good idea to create a new image (essentially BusyBox with certs) to chuck up on a repository somewhere. Such an &lt;a href="https://hub.docker.com/r/odise/busybox-curl"&gt;image does exist,&lt;/a&gt; but it&amp;rsquo;s very old.&lt;/p&gt;
&lt;p&gt;Another suggestion is just to bind mount the certs directory in the container to the host (as read only), and use the host&amp;rsquo;s certificates. This seems like a much simpler approach to me. It&amp;rsquo;s just an edit to the &lt;code&gt;docker-compose.yml&lt;/code&gt; or the run command.&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; example.com:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: httpd-example.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: ghcr.io/iankulin/example.com:latest
&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; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /etc/ssl/certs:/etc/ssl/certs:ro # Bind mount host&amp;#39;s SSL certs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;or&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 --name httpd-example.com -p 80:80 -v /etc/ssl/certs:/etc/ssl/certs:ro ghcr.io/iankulin/example.com:latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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>Certbot &amp; Let's Encrypt are great</title><link>https://blog.iankulin.com/certbot-lets-encrypt-are-great/</link><pubDate>Thu, 12 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/certbot-lets-encrypt-are-great/</guid><description>&lt;img src="https://blog.iankulin.com/images/certbot.png" width="847" alt=""&gt;
&lt;p&gt;I&amp;rsquo;ve been managing SSL certificates for my domains purchased from &lt;a href="https://porkbun.com/"&gt;PorkBun&lt;/a&gt; by going there every 90 days downloading the certificates, &lt;a href="https://blog.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/"&gt;joining them together&lt;/a&gt; to make the &lt;code&gt;fullchain.pem&lt;/code&gt; then &lt;code&gt;scp&lt;/code&gt;-ing them to my servers. That&amp;rsquo;s been sort of manageable, but less than ideal.&lt;/p&gt;
&lt;p&gt;It also doesn&amp;rsquo;t work for my Australian domains. Since there&amp;rsquo;s strict rules about who can own a domain in the &lt;code&gt;.au&lt;/code&gt; space (&lt;em&gt;you have to have some sort of right to the name - a random person can&amp;rsquo;t obtain the &lt;code&gt;coke.com.au&lt;/code&gt; domain unless that&amp;rsquo;s a trading name, a trademark, or something similar&lt;/em&gt;), they have to be managed by one of about eight organisations, and the offerings are much simpler.&lt;/p&gt;
&lt;p&gt;No problem though for two wonderful reasons - &lt;a href="https://letsencrypt.org/"&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; and &lt;a href="https://certbot.eff.org/"&gt;Certbot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s Encrypt is a free, automated, and open certificate authority (CA), run for the public’s benefit. It is a service provided by the Internet Security Research Group. They provide free TLS certificates to allow websites to use SSL.&lt;/p&gt;
&lt;p&gt;Certbot, managed by the Electronic Frontiers Foundation, is a utility to automatically obtain certificates for a website from Let&amp;rsquo;s Encrypt, and change the server configuration files to use them.&lt;/p&gt;
&lt;p&gt;This makes this whole process amazingly painless. There&amp;rsquo;s really no excuse for not adding this to your websites, and I&amp;rsquo;d highly encourage you to donate to both projects if you use Certbot.&lt;/p&gt;
&lt;h2 id="certbot"&gt;Certbot&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m running NGINX on Ubuntu LTS on my VPS&amp;rsquo;s, so installation was a snap (pun intended). I just followed the &lt;a href="https://certbot.eff.org/instructions?ws=nginx&amp;amp;os=ubuntufocal"&gt;instructions&lt;/a&gt; which involved installing the snap, adding a symlink to ensure it was in my path, then running the bot passing it a flag to say I was using NGINX.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-09-02-at-4.35.25-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-09-02-at-4.35.25-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It asks you a couple of questions, intelligently (by reading all the nginx conf files) then downloads the certificates and edits the nginx site conf files to use them. It also adds a systemd timer command to automate checking to see if they need renewed every couple of hours.&lt;/p&gt;
&lt;p&gt;Once that&amp;rsquo;s done, you just go back to your website and you&amp;rsquo;ve got the magical padlock, and won&amp;rsquo;t have to worry about it again due to the automatic renewal.&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>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>