LowEndBox - Cheap VPS, Hosting and Dedicated Server Deals

The Wrong Way to Use Let's Encrypt on a Private Network

https privateI’ve been working on a small app written in golang, and I’d like my local-area network connections to the golang-embedded webserver to be over https.  Most of the clients are going to be scripts running on various servers.

Unfortunately, since this is on a private IP range, we get into the whole certificate-verification thing.  There are a couple ways to solve this problem:

  1. Don’t check certificates, but that hardly seems optimal
  2. Create my own private CA, have all clients trust it, and use it to issue certificates

#2 is probably the right way to do this.

But something occurred to me.  All https really does is say “yes, somehost.com using that certificate has the private key to that certificate”.  There’s extended verification blah blah but really that’s all https does.  But we’re assuming that the client and the CA agree where somehost.com is.  What if they don’t…

I decided to try it.  I went to a server called dnsg1.lowend.party in the cloud and created an A record for server2.lowend.party that pointed to it (server1 is already in use for a different tutorial!)

Then on dnsg1 I installed certbot:

apt-get install certbot

And now using certbot, I get my Let’s Encrypt cert:

dnsg1:~ # certbot certonly --standalone -d server2.lowend.party
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Requesting a certificate for server2.lowend.party
Performing the following challenges:
http-01 challenge for server2.lowend.party
Waiting for verification...
Cleaning up challenges

- Congratulations! Your certificate and chain have been saved at:
Your key file has been saved at:
Your certificate will expire on 2022-07-03. To obtain a new or...
(blah blah)

So now I have a certficate for ‘server2.lowend.party’ that is signed by Let’s Encrypt.  Will it work on a private network?  You know I’m going to make you hit read more to find out.  And since you’re going to get a 1990s film meme I made just for this post, I don’t feel bad for it.

Reservoire Dogs

Reservoir Dogs, 1992


On the real server2.lowend.party, I compiled some basic golang code:

package main


func main() {
  http.HandleFunc( "/", func( res http.ResponseWriter, req *http.Request ) {
    fmt.Fprint( res, "It worked!\n" )
  } )

  log.Fatal( http.ListenAndServeTLS( ":9000", "fullchain.pem", "privkey.pem", nil ) )


All this code does is launch an https server on port 9000 and respond to requests to / with “It worked\n”.

Now let’s see if, indeed, it worked.  On another server, called crash, I edited /etc/hosts to set server2.lowend.party to my local IP.  Now when crash tries to find an address for server2.lowend.party, the entry in /etc/hosts will override DNS.

To be perfectly accurate, /etc/nsswitch.conf is consulted, and there you will find a line like (at least in stock Debian 11):

hosts:          files dns

This means consult “files” first (/etc/hosts) and then dns.  You might have other things in the list, such as ldap.  A comment in the file refers to the “GNU Name Service Switch” but this file existed in SunOS 4.x and probably earlier versions.  Well, maybe that was supposed to be called GNU/SunOS.

Anyway, back to our test.  Let’s use wget:

root@crash:~# wget https://server2.lowend.party:9000
--2022-04-04 17:43:25-- https://server2.lowend.party:9000/
Resolving server2.lowend.party (server2.lowend.party)...
Connecting to server2.lowend.party (server2.lowend.party)||:9000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10 [text/plain]
Saving to: ‘index.html’

index.html 100%[=================================================================>] 10 --.-KB/s in 0s

2022-04-04 17:43:25 (20.3 MB/s) - ‘index.html’ saved [10/10]

root@crash:~# cat index.html 
It worked!

Of course, eventually it will expire, but it be easy enough to script a renewal and rsync down to server2.  It’s cheesy, but it works!




  1. Simon:

    The other option (and what I do) is use the DNS-01 challenge to get a signed certificate that way. When requesting a certificate, it will add a TXT record to your DNS to check you control it. This of course can be automated if your DNS provider has an API (and almost all do).

    April 4, 2022 @ 10:23 pm | Reply
  2. kman:

    I have a similar set up for a local install of nextcloud, it required HTTPS to be turned on to remotely upload, all the devices at home use pihole which directs the devices to the local IP. When we are out the house I have a VPS with a public IP that is globally routed to that same url. So cert bot renewing on the vps and tunnelling traffic via VPN to home instances. Every now and then I need to copy over new Certs but works well.

    April 5, 2022 @ 6:12 am | Reply
  3. JT:

    If your DNS provider doesn’t have an API, you can use google-dns on a low end box somewhere on the internet to act as the API for your DNS. I’ve set it up on a $10 per year VPS and it works wonderfully well.

    April 5, 2022 @ 7:08 pm | Reply
  4. JT:

    That should read acme-dns, not google-dns….

    April 5, 2022 @ 7:08 pm | Reply
  5. Thank you for sharing this great article. 888b hopes you will have many more articles for everyone to read.

    April 11, 2022 @ 3:41 am | Reply
  6. Guy:

    > But we’re assuming that the client and the CA agree where somehost.com is

    There’s no verification going on. Your client may check a revocation list via CRLs but the certificate itself is verified by the signing chain, which relies on your local CA database. SSL certificate verification will work without any network connection whatsoever. Your CA also has no idea about the IP of the system using the certificate – there’s no metadata in the certificate about that. It is only tied to the unresolved hostname, called the Common Name.

    November 29, 2022 @ 10:16 am | Reply

Leave a Reply

Some notes on commenting on LowEndBox:

  • Do not use LowEndBox for support issues. Go to your hosting provider and issue a ticket there. Coming here saying "my VPS is down, what do I do?!" will only have your comments removed.
  • Akismet is used for spam detection. Some comments may be held temporarily for manual approval.
  • Use <pre>...</pre> to quote the output from your terminal/console, or consider using a pastebin service.

Your email address will not be published. Required fields are marked *